浅谈innodb_autoinc_lock_mode的表现形式和选值参考方法
前提条件,percona 5.6版本,事务隔离级别为rr
mysql> show create table test_autoinc_lock\g *************************** 1. row *************************** table: test_autoinc_lock create table: create table `test_autoinc_lock` ( `id` int(11) not null auto_increment, `a` int(11) default null, primary key (`id`), key `idx_a` (`a`) ) engine=innodb auto_increment=14 default charset=utf8 1 row in set (0.00 sec) mysql> select * from test_autoinc_lock; +----+------+ | id | a | +----+------+ | 1 | 1 | | 12 | 2 | | 2 | 3 | | 3 | 5 | | 4 | 7 | | 5 | 7 | | 6 | 9 | | 7 | 10 | +----+------+ 8 rows in set (0.00 sec)
条件1 innodb_autoinc_lock_mode设置为0
session1 begin;delete from test_autoinc_lock where a>7;//这时未提交 session2 mysql> insert into test_autoinc_lock(a) values(100);//gap锁的存在,这时处于锁等待 session3 mysql> insert into test_autoinc_lock(a) values(2);//这时同样处于等待状态,理论上这个不是gap锁的锁定范围,那么它是在等什么呢 session4 mysql> select * from information_schema.innodb_trx\g *************************** 1. row *************************** trx_id: 2317 trx_state: lock wait trx_started: 2016-10-31 19:28:05 trx_requested_lock_id: 2317:20 trx_wait_started: 2016-10-31 19:28:05 trx_weight: 1 trx_mysql_thread_id: 9 trx_query: insert into test_autoinc_lock(a) values(2) trx_operation_state: setting auto-inc lock trx_tables_in_use: 1 trx_tables_locked: 1 trx_lock_structs: 1 trx_lock_memory_bytes: 360 trx_rows_locked: 0 trx_rows_modified: 0 trx_concurrency_tickets: 0 trx_isolation_level: repeatable read trx_unique_checks: 1 trx_foreign_key_checks: 1 trx_last_foreign_key_error: null trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 10000 trx_is_read_only: 0 trx_autocommit_non_locking: 0
这时查看session3是等待自增锁,一直处于setting auto-inc lock状态
session2
error 1205 (hy000): lock wait timeout exceeded; try restarting transaction
这时session3锁等待超时退出
session3
这时再看session3可以发现insert完成。
mysql> select * from test_autoinc_lock; +----+------+ | id | a | +----+------+ | 1 | 1 | | 12 | 2 | | 13 | 2 | | 2 | 3 | | 3 | 5 | | 4 | 7 | | 5 | 7 | | 6 | 9 | | 7 | 10 | +----+------+ 9 rows in set (0.00 sec)//注意看这时的最大自增值是13,也就是之前自增最大值上+1,也就是说session2后来释放了预计生成的自增id,将13留给了session3,自增id值的申请完全是串行顺序的。
结论:innodb_autoinc_lock_mode为0时的,也就是官方说的traditional
级别,该自增锁是表锁级别,且必须等待当前sql执行完成后或者回滚掉才会释放,这样在高并发的情况下可想而知自增锁竞争是比较大的。
条件2 innodb_autoinc_lock_mode设置为1
session1 mysql> begin; query ok, 0 rows affected (0.00 sec) mysql> delete from test_autoinc_lock where a>7; query ok, 2 rows affected (0.00 sec) mysql> select * from test_autoinc_lock; +----+------+ | id | a | +----+------+ | 1 | 1 | | 12 | 2 | | 13 | 2 | | 2 | 3 | | 3 | 5 | | 4 | 7 | | 5 | 7 | | 6 | 9 | | 7 | 10 | +----+------+ 9 rows in set (0.00 sec)//注意看这时的最大自增值是13 session2 mysql> insert into test_autoinc_lock(a) values(100);//同样gap锁的存在,这时处于锁等待 session3 mysql> insert into test_autoinc_lock(a) values(5); query ok, 1 row affected (0.00 sec) mysql> select * from test_autoinc_lock; +----+------+ | id | a | +----+------+ | 1 | 1 | | 12 | 2 | | 13 | 2 | | 2 | 3 | | 3 | 5 | | 15 | 5 | | 4 | 7 | | 5 | 7 | | 6 | 9 | | 7 | 10 | +----+------+ 10 rows in set (0.00 sec)//session3直接完成了,并且注意观察插入的自增id值是15,也就是跳过了预计分配给session2的14,可以看到自增id值立马就分配给了session3,而不必等session2执行完成
结论:innodb_autoinc_lock_mode为1时的,也就是官方说的consecutive
级别,这时如果是单一的insert sql,可以立即获得该锁,并立即释放,而不必等待当前sql执行完成(除非在其他事务中已经有session获取了自增锁)。另外当sql是一些批量insert sql时,比如insert into ...select ...,load data,replace ..select..时,这时还是表级锁,可以理解成退化为必须等待当前sql执行完才释放。
可以认为,该值为1时是相对比较轻量的锁,也不会对复制产生影响,唯一的缺陷是产生的自增值不一定是完全连续的(不过个人认为这个往往不是很重要,也没必要根据自增id值来统计行数之类)
条件3 innodb_autoinc_lock_mode设置为2
先说结论:当innodb_autoinc_lock_mode设置为2时,所有insert种类的sql都可以立马获得锁并释放,这时的效率最高。但是会引入一个新的问题:当binlog_format为statement时,这时的复制没法保证安全,因为批量的insert,比如insert ..select..语句在这个情况下,也可以立马获取到一大批的自增id值,不必锁整个表,slave在回放这个sql时必然会产生错乱。我们做个测试验证复制不是安全的。
master session1 mysql> show variables like '%binlog_for%'; +---------------+-----------+ | variable_name | value | +---------------+-----------+ | binlog_format | statement | +---------------+-----------+ 1 row in set (0.00 sec) mysql> insert into test_autoinc_lock(a) select * from test_auto; query ok, 8388608 rows affected, 1 warning (29.85 sec) records: 8388608 duplicates: 0 warnings: 1 master session2(注意session2在session1执行完成之前执行) mysql> insert into test_autoinc_lock(a) values(2); query ok, 1 row affected (0.01 sec) mysql> select * from test_autoinc_lock where a=2; +---------+------+ | id | a | +---------+------+ | 1376236 | 2 | +---------+------+ 1 row in set (0.00 sec) slave session1(这时可看到1376236主键冲突) mysql> show slave status\g *************************** 1. row *************************** slave_io_state: waiting for master to send event master_host: 10.9.73.139 master_user: ucloudbackup master_port: 3306 connect_retry: 60 master_log_file: mysql-bin.000006 read_master_log_pos: 75823243 relay_log_file: mysql-relay.000002 relay_log_pos: 541 relay_master_log_file: mysql-bin.000006 slave_io_running: yes slave_sql_running: no replicate_do_db: replicate_ignore_db: replicate_do_table: replicate_ignore_table: replicate_wild_do_table: replicate_wild_ignore_table: last_errno: 1062 last_error: error 'duplicate entry '1376236' for key 'primary'' on query. default database: 'test'. query: 'insert into test_autoinc_lock(a) select * from test_auto' skip_counter: 0 exec_master_log_pos: 75822971
我们这时解析下主库的binlog不难发现问题原因,第一条批量insert还没执行完时,第二条简单insert执行时获得了自增id值为1376236的锁,这时在主库写入是没有问题的,但是反应到从库时,因为是基于statement的复制,必然出现主键冲突。
set insert_id=1376236/*!*/; #161031 21:44:31 server id 168380811 end_log_pos 75822940 crc32 0x65797f1c query thread_id=20 exec_time=0 error_code=0 use `test`/*!*/; set timestamp=1477921471/*!*/; insert into test_autoinc_lock(a) values(2) /*!*/; # at 75822940 #161031 21:44:31 server id 168380811 end_log_pos 75822971 crc32 0xbb91449d xid = 274 commit/*!*/; # at 75822971 #161031 21:44:26 server id 168380811 end_log_pos 75823050 crc32 0xa297b57b query thread_id=57 exec_time=30 error_code=0 set timestamp=1477921466/*!*/; begin /*!*/; # at 75823050 # at 75823082 #161031 21:44:26 server id 168380811 end_log_pos 75823082 crc32 0xa5aa31a1 intvar set insert_id=1/*!*/; #161031 21:44:26 server id 168380811 end_log_pos 75823212 crc32 0x470282ba query thread_id=57 exec_time=30 error_code=0 set timestamp=1477921466/*!*/; insert into test_autoinc_lock(a) select * from test_auto
总结:
1 innodb row复制时,可将innodb_autoinc_lock_mode设置为2,这时可在所有insert情况下表获得最大并发度
2 innodb statement复制时,可将innodb_autoinc_lock_mode设置为1,保证复制安全的同时,获得简单insert语句的最大并发度
3 myisam引擎情况下,无论什么样自增id锁都是表级锁,设置innodb_autoinc_lock_mode参数无效(测试略)
4 实际上提问者说到的在innodb引擎下自增id值作为主键的情况下,相比uuid或者自定义的主键,是可以提到插入速度的,因为innodb是主键聚集索引,实际的主键值必须按照主键顺序存取,那么自增id本身就是升序的,那么在插入数据时,底层就不必再做额外的排序操作,也减少了索引页分裂的次数,从而大大增加insert速度(除非其他方案也能保证主键完全自增)
以上这篇浅谈innodb_autoinc_lock_mode的表现形式和选值参考方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。