———————————
1.创建测试表
CREATE TABLE t(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
k INT,
c CHAR(1),
UNIQUE KEY(k)) ENGINE=InnoDB;
2.当顺序执行如下SQL时
SQL1–INSERT INTO t(k) VALUES (1), (2), (3) ON DUPLICATE KEY UPDATE c=’1′;
SQL2–INSERT INTO t(k) VALUES (2), (4), (5) ON DUPLICATE KEY UPDATE c=’2′;
SELECT * FROM t;
id k c
1 1 NULL
2 2 2
3 3 NULL
4 4 NULL
5 5 NULL
在Binlog里记录的是先SQL1,再SQL2
使用DEBUG_SYNC来模拟并发的场景,并发执行上述两个SQL时,会得到不同的结果,具体见test case
SELECT * FROM t;
id k c
1 1 NULL
4 2 1
5 4 1
6 5 NULL
而在备库的执行结果则是按照串行执行,执行结果为:
1 1 NULL
2 2 2
3 3 NULL
4 4 NULL
5 5 NULL
如果binlog_format为statement模式时,这种情况就会造成主备数据不一致,因为在备库是按照串行化执行的。
根据test case,其模拟的场景是SQL1插入第一行等待,SQL2完成,SQL1再插入剩余的记录。
当insert出现dup key时如下代码逻辑对next_insert_id做了调整
sql/sql_insert.cc
write_record函数
1649 if (table->next_number_field)
1650 table->file->adjust_next_insert_id_after_explicit_value(
1651 table->next_number_field->val_int());
其中
table->next_number_field->val_int()是冲突记录的主键值,这样我们就可以理清这里的逻辑了
SQL1获得的自增区间为1,2,3
SQL2获得的自增区间为4,5,6
如下执行序列:
SQL1:1 1 NULL next_insert_id=2
SQL2:4 2 NULL
SQL2: 5 4 NULL
SQL2:6 5 NULL
SQL1: 试图插入2 2 NULL,uniquekey冲突,改为update,记录为4 2 1,
next_insert_id=4+1=5
SQL1: 试图插入5 3,NULL,主键冲突,改为update,记录为5 4 1
因此最后的结果是
1 1 NULL
4 2 1
5 4 1
6 5 NULL
问题在于即使唯一索引(非主键)冲突,也可能会调整next_insert_id(当冲突记录主键值>当前next_insert_id时)。Percona已经在MySQL Buglist上report了这个bug,并且patch已经release
patch很简单,对上述情况区别对待。给table增加了一个成员变量next_number_field_updated
当在INSERT … ON DUPLICATE KEY UPDATE后,如果自增列在UPDATE中,设置该变量值为TRUE。
这样在函数write_record中,如果遇到DUP KEY,当next_number_field_updated为TRUE时,设置next_insert_id值(按照原先的逻辑adjust_next_insert_id_after_explicit_value),否则next_insert_id保留为当前的table->file->insert_id_for_cur_row,也就是该记录获得的自增值。