—————————————————
MySQL bug#61209,changelog如下,在5.5.23被fix
InnoDB: Running concurrent bulk inserts on a server with auto_increment_offset=1
,auto_increment_increment
greater than 1, and innodb_autoinc_lock_mode=1
could result in intermittent errors like the following, even with the primary key set to auto_increment and omitted from the INSERT
statement:
Duplicate entry 'value' for key 'PRIMARY'
The workaround was to set auto_increment_offset=1
or innodb_autoinc_lock_mode=0
(“traditional”). (Bug #13817703, Bug #61209)
之前已经介绍过了innodb 如何处理auto inc的代码流程,现在来看看在innobase_next_autoinc里是如何来计算的。
假定我们设置:
set global auto_increment_increment=3;
set global auto_increment_offset=2;
例如对于一条简单的SQL:insert into tt values (NULL),(NULL)
函数innobase_next_autoinc的参数:
–current 当前的值 (1)
–increment 申请保留的区间长度 (6)
–offset auto_increment_offset的值 (2)
–max_value 该自增列类型最大容许的值 (2147483647)
1.当offset > increment时,设置offset=0,也就是忽略掉auto_increment_offset
2.确定next_value的值
a.当max_value <= current时,next_value = max_value; (溢出)
b.当offset<=1时(1或0视为等同):
如果max_value – current <= increment , next_value = max_value; (溢出)
否则next_value = current + increment
c.当max_value > current,且offse>1时
next_value = ((|current – offset| / increment) + 1)*increment+offset
可以看出来代码逻辑还是很简单的
现在在来看看test case
session 1
SQL1:insert into tt values (NULL),(NULL);
{interval_min = 2, interval_values = 2, interval_max = 8, next = 0x0}
innobase_next_autoinc的返回值为((1-2)/6+1)*6+2=8
table->autoinc=8
插入记录2,5
SELECT GET_LOCK(‘a’, 100);
session 2
SQL2:INSERT INTO tt (a) VALUES (NULL), (NULL), (NULL + GET_LOCK(‘a’, 1800));
{interval_min = 8, interval_values = 3, interval_max = 17, next = 0x0}
调用函数innobase_next_autoinc两次
第一次返回值((8-2)/9+1)*9+2=11
table->autoinc=11
第二次调用是在写第3个记录时(在write_row函数中调用)
返回值为(((table->autoinc-auto_increment_offset)/auto_increment_increment+1))* auto_increment_increment+ auto_increment_offset
=((11-2)/3+1)*3+2=14
table->autoinc=14
在尝试插入第三个记录时会block住
session 1
SQL3:INSERT INTO tt (a) VALUES (NULL), (NULL), (NULL);
{interval_min = 14, interval_values = 3, interval_max = 23, next = 0x0}
同样调用函数innobase_next_autoinc两次
第一次返回值为20
第二次返回值为24
插入记录14,17,20
而每个预留区间的最小值是和table->auto_inc相关的,显然这里自增值发生了错乱,SQL3应该从17开始而不是14
官方patch请点击这里
主要修改了innobase_next_autoinc函数
SQL1的返回值为8
SQL2的返回值为17
SQL3的返回值为26
实际上稍微扩大了table->auto_inc的值,以避免出现交叉。加上patch后的innobase_next_autoinc函数的参数修改为:
–current 当前值
–need 需要自增值的个数
–step auto_increment_increment
–offset auto_increment_offset
–max_value 该自增类型的最大可能值
next_value=(|offset – current| / step)
next_value*=step
next_value+=( need * step +offset)
因此
SQL1的返回值为8 (|2-1|/3=0; 0*3; 0+2*3+2=8)
SQL2的返回值为17(|2-8|/3=2;2*3=6;6+3*3+2=17)
SQL3的返回值为26(|2-17|/3=5;5*3=15;15+3*3+2=26)
这样就可以保证table->autoinc的值不会重叠了。