先描述下故障吧
step0: 环境介绍 1. MySQL5.6.27 2. InnoDB 3. Centos 基本介绍完毕,应该跟大部分公司的实例一样 CREATE TABLE `new_table` ( `id` int(11) NOT NULL AUTO_INCREMENT, `x` varchar(200) DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5908151 DEFAULT CHARSET=utf8 CREATE TABLE `old_table` ( `id` int(11) NOT NULL AUTO_INCREMENT, `xx` varchar(200) DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5908151 DEFAULT CHARSET=utf8 step1: 业务需要导入历史数据到新表,新表有写入 1. insert into new_table(x) select xx from old_table 2. 批量插入在new_table上 step2: 结果 show processlist; 看到好多语句都处于executing阶段,DB假死,任何语句都非常慢,too many connection step3: 查看innoDB状况 show engine innodb statu\G 结果: ==lock== ---TRANSACTION 7509250, ACTIVE 0 sec setting auto-inc lock --一堆 TABLE LOCK table `xx`.`y'y` trx id 7498948 lock mode AUTO-INC waiting --一堆让问题再次发生才好定位解决问题
表结构 | t_inc | CREATE TABLE `t_inc` ( `id` int(11) NOT NULL AUTO_INCREMENT, `x` varchar(199) DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5908151 DEFAULT CHARSET=utf8 | CREATE TABLE `t_inc_template` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `cookie_unique` varchar(255) NOT NULL DEFAULT '' COMMENT '', PRIMARY KEY (`id`), ) ENGINE=InnoDB AUTO_INCREMENT=5857489 DEFAULT CHARSET=utf8 step1 session1:insert into t_inc(x) select cookie_unique from t_inc_template; session2:mysqlslap -hxx -ulc_rx -plc_rx -P3306 --concurrency=10 --iterations=1000 --create-schema='lc' --query="insert into t_inc(x) select 'lanchun';" --number-of-queries=10 产生并发,然其自动分配自增id。 step2:观察 | 260126 | lc_rx | x:22833 | NULL | Sleep | 8 | | NULL | | 260127 | lc_rx | x:22834 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260128 | lc_rx | x:22835 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260129 | lc_rx | x:22836 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260130 | lc_rx | x:22837 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260131 | lc_rx | x:22838 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260132 | lc_rx | x:22840 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260133 | lc_rx | x:22839 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260134 | lc_rx | x:22842 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260135 | lc_rx | x:22841 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | | 260136 | lc_rx | x:22843 | lc | Query | 8 | executing | insert into t_inc(x) select 'lanchun' | step3 show engine innodb status TABLE LOCK table `lc`.`t_inc` trx id 113776506 lock mode AUTO-INC waiting 一堆这样的waiting 然后卡死好了问题已经复现,大概也知道是什么原因造成了,那就是:AUTO-INC lock
接下来聊聊自增锁
表结构:a primary key auto_increment,b varchar(3)
time_logic_clocksession1:bulk insert()session2: insert like01,A12,AA23,B34,C45,CC56,D最终的结果是:
ab1A2AA3B4C5CC6D slave的最终结果因为binlog中session2的语句先执行完,导致结果为
ab1AA2CC3A4B5C6D因为RBR都是根据row image来的,跟语句没关系的。
好了,通过以上对比分析,相信大家都知道该如何抉择了吧?
由于innodb_autoinc_lock_mode = 2是语句级别的锁,那么就有可能造成 后面的id先提交,前面的id后提交
举个例子:
session A: begin; insert into xx values() ; --这时候的自增id 是100 session B: begin insert into xx values() ; --这时候的自增id 是101 session B: commit; --意味着id=101的记录先插入到数据库 session A: commit; --意味着id=100的记录后插入到数据库最后,对于数据库来说,没有大问题,因为数据都插入进来了,只是后面的id先插入进来而已。
但是有的业务就有问题:比如,某些业务根据自增id进行遍历
select * from xx where id>1 limit N select * from xx where id>1+N limit N select * from xx where id>1+N+N limit N如果id是顺序插入的,就没问题。 如果后面的id先插入进来(比如id=101),那么id=100还没提交的id就被程序忽略掉了,由此对业务来说就丢了id=100 这条记录
解决方法:where id>N and add_date< (NOW() - INTERVAL 5 second) 取前5s的数据,降低并发写入带来的困扰
