今天碰到一个问题,数据库表有一个字段的值是通过sequence来生成的,但是,获取到的数值却并不连续,而且是非常有规律的一下子就跳跃到21,刚开始是怀疑代码的问题,但是代码当中根本就没有操作这个sequence,写代码的人甚至连这个sequence的名字都不知道。然后,就发现, sequence有一个属性是cache size,它的值是20,莫非跟这个有关系?果然如此!

SEQUENCE — cache 的用处

在创建序列的语法中,有一个子句为 cache,它的用处是缓存指定个数的序列值。比如你设置的 cache 是20,那么在获取 nextval 时,Oracle 会直接从 cache 中取下一个序列值,如果 cache 中缓存的序列值没有了(比如 cache 中的序列值用完了,或者被手工清空了),那么 Oracle 会再次产生20个序列值,并放置 cache 中供使用,这样有助于提高序列值的获取速度。


下面我们做个测试:

SQL> select * from v$version;

BANNER

----------------------------------------------------------------

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod

PL/SQL Release 10.2.0.1.0 - Production

CORE    10.2.0.1.0      Production

TNS for 32-bit Windows: Version 10.2.0.1.0 - Production

NLSRTL Version 10.2.0.1.0 - Production

SQL> create sequence SEQ_TEST1

2  minvalue 1

3  maxvalue 1000

4  start with 1

5  increment by 1

6  cache 20

7  order;

序列已创建。

-- 刚刚创建的序列必须先用 nextval 来获取一个初始值

SQL> select SEQ_TEST1.currval from dual;

select SEQ_TEST1.currval from dual

*

ERROR 位于第 1 行:

ORA-08002: 序列 SEQ_TEST1.CURRVAL 尚未在此会话中定义

SQL> select SEQ_TEST1.nextval from dual;

NEXTVAL

----------

1

SQL> select SEQ_TEST1.currval from dual;

CURRVAL

----------

1

-- 清空 cache 中缓存的序列值

SQL> alter system flush shared_pool;

系统已更改。

-- 查询当前值,没有变化,仍是1

SQL> select SEQ_TEST1.currval from dual;

CURRVAL

----------

1

-- 查询下一个值:Oracle 发现 cache 中的序列值没有了,会再次产生20个序列值供使用。

-- 所以这里得到的结果不是2,而是21。

SQL> select SEQ_TEST1.nextval from dual;

NEXTVAL

----------

21

-- 再试一次

SQL> alter system flush shared_pool;

系统已更改。

SQL> select SEQ_TEST1.currval from dual;

CURRVAL

----------

21

SQL> select SEQ_TEST1.nextval from dual;

NEXTVAL

----------

41

-- 问题:Oracle 下一次取的20个值是从哪里开始计算的呢,是 currval + 20,

-- 还是每个 cache + 20 呢?我们试验一下。

SQL> select SEQ_TEST1.nextval from dual;

NEXTVAL

----------

42

-- 现在序列的当前值是42。如果是用 currval + 20 来计算,那么清空 cache 后,

-- 获得的 nextval 应该是62;如果是 cache + 20,那应该是 61。

-- 看看实验结果吧:

SQL> alter system flush shared_pool;

系统已更改。

SQL> select SEQ_TEST1.currval from dual;

CURRVAL

----------

42

SQL> select SEQ_TEST1.nextval from dual;

NEXTVAL

----------

61

结论:cache 可以用来提高序列值的获取速度,但有可能会浪费一些序列号,应该根据实际情况来设置 cache 的大小。

也就是说,oracle会把sequence缓存到shared pool里面,如果数据库down掉了,下次再启动的时候就会出现gap。

但是,我们的数据库并没有down掉啊,一直都在正常运行呢!

在这里面tom大师说了,sequence的唯一的用处是保证数值的唯一性,既不保证gap free,也不保证order。

you cannot expect a sequence to return gap free values. a sequence has one purpose: assign unique numbers to stuff. Nothing else. there will be gaps, gaps are normal, expected, good, ok, fine. they will be there, there is no avoiding them. This is not a problem, it is expected, it is not "fixable" - a "rollback" for example will generate a gap if some session selected a sequence.

Do not assume they are gap free and all is well in the world.


还说,不用担心sequence会用完,

set cache to 10000 - but don't lower it.

you'll never run out of values. even if you lose 10,000 per second

1* select 999999999999999999999999999/10000/60/60/24/365 from dual

ops$tkyte%ORA9IR2> /

999999999999999999999999999/10000/60/60/24/365

----------------------------------------------

3.1710E+15

that is how many years you would have before running out of values

什么时候会出现gap呢?

(1)事务回滚会导致gap

(2)数据库down掉会导致gap

(3)just normal aging of things out of the SGA can cause it.内存用完了也会导致gap的出现。原来我们的问题在这里。我们就是在产生下一个序列号之前使用了一个非常消耗内存的工具,导致oracle缓存的sequence被替换出内存。


但是,tom大师同时也说了,千万不要设置nocache,alter sequence XXX nocache;

do not use nocache - that would be like setting "slow=true" in your init.ora parameter file.

这篇文章有启用和禁用cache在性能方面的数值比较。

如果我们想不出现gap该如何做呢?

那肯定就不能使用sequence了。

CACHE or NOCACHE, if your requirement is truly to make sure there are no gaps, then a sequence is the wrong solution. You may get fewer gaps with NOCACHE, so they may be less noticeable, but you have accepted the possibility of gaps for a requirement that 'demands' no gaps. If you want to use a sequence, you must first negotiate that 'gap-free' requirement away, and make sure none of the design depends on the keys being 'gap-free'. If the requirement sticks, then you have to use some other mechanism - maybe a table of available keys, or something else. Also make sure there is no requirement that they are assigned in order, and no design dependency anywhere that they be assigned in order. Use a timestamp to keep track of 'in what order were these done'; if that's not enough, then you need more thought and more work.

有人说:每次从表里面查询出最大的,然后加1,作为下一条记录的id,但是,如果最大的记录被删掉以后,下一次再插入的记录的id和被删掉的id是一样的,这样的数据会引发混乱。

还有人说,另外定义一个专门存放最大id的表,就两列,表名和当前最大的id,然后需要往表里面插入的时候,select for update,得到下一个id,这就保证id一直往上增加。

这两种方式都得注意,必须要序列化操作,防止多线程导致id重复。


小小菜鸟一枚