今天同事在查询V$SQL视图的时候发现BUFFER_GET列出现了负值。研究了一下,感觉应该是Oracle的整型数溢出了。
查询Oracle的V$SQL视图,发现其中一条SQL:SELECT 1 FROM DUAL的BUFFER_GETS变成了负值:
SQL> select to_char(buffer_gets) from v$sql where sql_text = 'SELECT 1 FROM DUAL';
TO_CHAR(BUFFER_GETS)
----------------------------------------
-2069050280
1 row selected.
为什么会出现负数呢,除了bug外似乎没有别的解释,不过即使是bug,Oracle也没有道理写一个负数到动态视图中的。
观察一下执行次数:
SQL> select executions, to_char(buffer_gets) from v$sql where sql_text = 'SELECT 1 FROM DUAL';
EXECUTIONS TO_CHAR(BUFFER_GETS)
---------- ----------------------------------------
742004326 -2068982372
对于DUAL表的扫描,一般每次执行都是3个逻辑读。
SQL> select 742004326*3 from dual;
742004326*3
-----------
2226012978
数量级是一样的,只不过buffer_gets的值是负的。
一般来说出现负值都是由于存储数值的变量发生溢出造成的。Oracle的number类型是38位,存储最大数值可以达到10的125次方,不可能在这么小的数值发生溢出。
不过Oracle的核心程序是用C语言写的,C的int型变量是32位的:
SQL> select to_char(power(2, 32)) from dual;
TO_CHAR(PO
----------
4294967296
这个上限值是对于unsigned int而言的,对于可以表示正书和负数的int类型而言,上限仅仅是这个值的一半。
SQL> select 742004326 * 3 - power(2, 32) / 2 from dual;
742004326*3-POWER(2,32)/2
-------------------------
78529330
显然是由于BUFFER_GETS的值已经超出了这个上限值。而且C采用的补码的算法,当超过最大值2147483647后,数值开始从-2147483648向-1递增。
观察上面两次对BUFFER_GETS的查询,该值已经从-2069050280增加到了-2068982372。
Oracle在这里应该使用UNSIGNED INT类型或者LONG类型来保存数值,就不会出现这个问题了。