[MySQL] AUTO_INCREMENT lock Handing in InnoDB
MySQL AUTO_INCREMENT lock Handing in InnoDB
在MySQL的表设计中很普遍的使用自增长字段作为表主键, 实际生产中我们也是这样约束业务开发同学的, 其中的优势:
- 无意义自增长字段对业务透明, 后期不会存在更新主键等操作, 这在多数据中心同步中避免了很多问题.
- InnoDB表是IOT表, 数据按照主键组织存储, B-Tree叶子节点连续递增, 写入时保证了性能, 避免了频繁InnoDB page分裂, 并且也提高了空间利用率.
- 自增长字段只占4或者8个字节, 代价小. 一个page内存储更多的数据, buffer pool缓存命中概率更高.
当然, 使用自增长字段作为主键也会带来一些弊端:
- auto_increment热点争用
- replace逻辑可能导致主备auto_increment不一致, 在主备切换后会出现duplicate key问题
最近身边同学出现一起线上故障就是由于auto_increment使用不当/不够了解导致的, 因此花点时间探究下InnoDB引擎auto_increment问题, 总结一下, 分享给大家.
InnoDB为auto_increment列提供了可配置的锁机制, 在写入数据时提升SQL的并发和性能. auto_increment列必须是主键, 或者是索引的一部分(索引列的第一列).
- InnoDB AUTO_INCREMENT Lock Modes(锁级别)
- InnoDB AUTO_INCREMENT Lock Mode Usage Implications(用法示例)
- InnoDB AUTO_INCREMENT Counter Initialization(计数初始化)
InnoDB AUTO_INCREMENT Lock Modes
这部分主要分析不同auto_increment lock mode用以产生自增值的行为, 以及对主备复制的影响. Auto-increment lock modes配置在my.cnf中的参数innodb_autoinc_lock_mode
, mysqld启动参数.
"INSERT-like"语句
所有向表里插入新行的语句, 包括
INSERT
,INSERT...SELECT
,REPLACE
,REPLACE...SELECT
, 以及LOAD DATA
. 包括"Simple inserts", "Bulk inserts", "Mixed-mode inserts"."Simple inserts"
Simple inserts指那种可以事先确定插入行数的语句, 包括单行和多行插入, 不包含嵌套自查询的
INSERT
和REPLACE
语句(这样的语句也要除外:INSERT...ON DUPLICATE KEY UPDATE
)"Bulk inserts"
Bulk inserts指那种事先无法确定插入行数的语句, 包括
INSERT...SELECT
和REPLACE...SELECT
, 以及LOAD DATA
语句. InnoDB在处理每一行的时候为新纪录分配一个自增值."Mixed-mode inserts"
Mixed-mode inserts指的是simple inserts类型中有些行指定了auto_increment列的值, 有些没有指定, 譬如:
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
另外一种Mixed-mode inserts类型是
INSERT ... ON DUPLICATE KEY UPDATE
, 最坏的情况是"insert"其实是"update", 这可能会导致分配的auto_increment值没有被使用.innodb_autoinc_lock_mode
有3种配置模式, 0/1/2, 分别对应"传统模式", "连续模式", "交错模式".innodb_autoinc_lock_mode = 0 (传统锁模式)
保持了MySQL 5.1版本中相同的行为, 向后兼容.
在这种锁模式下, 所有"INSERT-like"语句在插入表AUTO-INCREMENT列时获取表级别的
AUTO-INC
锁, 该锁会持有到语句执行结束(而非事务结束), 确保auto-increment值以可预期, 可重复, 连续的序列顺序分配给INSERT
语句在SBR主备同步模式下, 可以保证同一条SQL语句复制到备库时可以产生和主库相同的auto-increment值.
Multiple-INSRT
语句在备库执行产生确定性的结果, 就如在主库上执行的一样. 如果Multiple-INSRT
语句产生的auto-increment值是交错的, 那么并发的两条INSERT
语句将产生不确定性的结果, 那么也就不能可靠的使用SBR模式复制主备数据同步.
假如这样一种case:
CREATE TABLE t1 (
c1 INT(11) NOT NULL AUTO_INCREMENT,
c2 VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (c1)
) ENGINE=InnoDB;
有两个事务在运行, 都向表中插入auto-increment列.
tx1:
INSERT...SELECT
语句, 插入100行.tx2:
INSERT
语句.tx1: INSERT INTO t1 (c2) SELECT 1000 rows from another table ...
tx2: INSERT INTO t1 (c2) VALUES ('xxx');
在tx1中, InnoDB无法事先预期到
INSERT...SELECT
语句产生多少行数据, 在语句执行过程中分配auto-increment值(table-level lock), 持有锁直到语句执行结束. 同一时刻只有一个INSERT
语句可以执行, 并且保证产生的auto-increment值不会交错. tx1事务执行INSERT...SELECT
语句产生连续的auto-increment值, tx2事务执行INSERT
语句产生的auto-increment值要么小于所有tx1语句产生的auto-increment值, 要么大于所有tx1语句产生的auto-increment值, 这要取决于哪一个事务先执行.当使用SBR模式主备复制, 或者恢复场景时, 回放binary log时只要SQL语句以相同的顺序执行, 其结果就如当时tx1和tx2运行时一样.
所以, table-level lock持有到语句执行结束来产生auto-increment值安全的保证了SBR模式的主备数据同步. 但是, table-level lock也限制了SQL(insert)语句的并发性能.
在"连续锁模式"中, InnoDB在执行"Simple inserts"时避免了table-level AUTO-INC lock, 同时也保证了安全的SBR主备数据复制.
如果在复制或者恢复场景中不使用binary log回放SQL语句, 那么"交错锁模式"可以被使用来消除table-level AUTO-INC lock以提升并发和性能. 但是这会带来并发执行SQL语句时产生间隙的auto-increment值或者交错的auto-increment值.
innodb_autoinc_lock_mode = 1 (连续锁模式)
这是InnoDB默认的锁模式.
"Bulk inserts"使用特殊的AUTO-INC table-level lock, 并且持有锁到语句结束. 包括所有的
INSERT...SELECT
,REPLACE...SELECT
, 以及LOAD DATA
语句. 同一时刻只有一个语句可以持有AUTO-INC table-level lock."Simple inserts"避免le使用table-level AUTO-INC lock, 而是使用互斥锁(mutex, 更轻量级锁)控制获取需要的auto-increment值, 只有在分配auto-increment值期间持有, 并不是语句执行结束. 如果有事务持有table-level AUTO-INC lock, 那么"Simple inserts"将会向"Bulk inserts"一样等待AUTO-INC lock.
这个锁模式确保了所有"INSER-like"语句产生连续的auto-increment值(包括哪些事先不确定插入行数的"INSERT"语句), 这些操作在SBR模式数据复制时都是安全的.
简单来说, 这个锁模式明显的提升了在使用SBR复制时的可扩展性以及安全性. 更深入的, 像"传统锁模式"那样对于特定的SQL语句分配的auto-increment值是连续的.
一个例外是"Mixed-mode inserts", 用户指定了一些auto-increment值, 有些则没有指定, "Simple inserts"插入多行数据. 对这些插入, InnoDB分配了比插入行数更多的auto-increment值. 所有auto-increment自动的连续产生(所有比最近之前执行的语句的auto-increment值大), 剩余没用的auto-increment值就忽略(丢失)不用了.
innodb_autoinc_lock_mode = 2 (交错锁模式)
这个锁模式下, 所有"INSERT-like"语句不使用table-level AUTO-INC lock, 同一时刻SQL语句可以并发执行. 这是最快的, 更高扩展性的锁模式, 但是在使用SBR复制或者恢复场景中回放binary log时却是不安全的.
这个锁模式下, auto-increment值在所有并发执行的"INSERT-like"语句中保持唯一以及单调增长. 同一时刻多条SQL语句产生的交错的auto-increment值.
如果只有"Simple inserts"执行, 那么将不会产生的间隙的auto-increment值(排除"Mixdex-mode inserts"); 当执行"Bulk-inserts"时, 任何执行的SQL都可能产生间隙的auto-increment值.
InnoDB AUTO_INCREMENT Lock Mode Usage Implications
Using auto-increment with replication
SBR复制, 使用innodb_autoinc_lock_mode = 0/1是安全的.
innodb_autoinc_lock_mode = 2或者主备配置不同的lock mode将不能确保主备上产生相同的auto-increment值.
RBR(或者mixed-format)复制, 所有lock mode都是安全的.
因为行级别的复制对SQL的执行顺序不敏感. (mixed-format复制将任何SBR复制不安全的SQL语句使用RBR复制)
"Lost" auto-increment values and sequence gaps
在所有的lock mode中, 如果事务在产生auto-increment后执行roll back, 那么这个auto-increment值将"lost", 这样的auto-increment值是不会被重用的. 在auto-increment列的表中可能会出现间隙的自增长值现象.
Specifying NULL or 0 for the AUTO_INCREMENT column
在所有的lock mode中, 如果用户在
INSERT
语句中为auto-increment列指定了NULL或者0, InnoDB会将其对待为像没有特别指定那样, 为其产生一个新的auto-increment值.Assigning a negative value to the AUTO_INCREMENT column
在所有的lock mode中, 为auto-increment列指定一个负值是没有定义auto-increment机制行为的.
If the AUTO_INCREMENT value becomes larger than the maximum integer for the specified integer type
在所有的lock mode中, 如果指定auto-increment值比auto-increment列类型可以存储的最大值还大是没有定义auto-increment机制行为的.
Gaps in auto-increment values for "bulk inserts"
如果innodb_autoinc_lock_mode = 0/1, "Bulk-inserts"语句产生的auto-increment值是连续的, 并且没有间隙. 因为table-level AUTO-INC lock持有直到语句执行结束, 并且同一时刻只有一条SQL在执行.
如果innodb_autoinc_lock_mode = 2, "Bulk-inserts"语句产生的auto-increment值可能出现间隙(出现的条件是: 存在并发执行"INSERT-like"语句).
Auto-increment values assigned by "mixed-mode inserts"
考虑"mixed-mode insert", 当"Simple inserts"指定了一些插入行的auto-increment值(并发所有), 这样的SQL在不同的lock mode下表现的行为是不一样的.
譬如: 假如表t1的列c1是auto-increment的, 最新自动产生的序列值是100.
mysql> CREATE TABLE t1 (
-> c1 INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> c2 CHAR(1)
-> ) ENGINE = INNODB;
考虑下面的"mixed-mode insert"语句:
mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
innodb_autoinc_lock_mode = 0, 4条新插入的行数据是:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1 | c2 |
+-----+------+
| 1 | a |
| 101 | b |
| 5 | c |
| 102 | d |
+-----+------+
下一个可用的auto-increment值将会是103, 因为auto-increment值是一次分配一个, 并不是SQL语句开始执行的时候分配. 这个结果是可以确定的, 无论是否并发执行"INSERT-like"语句.
innodb_autoinc_lock_mode = 1, 4条新插入的行数据依然是:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1 | c2 |
+-----+------+
| 1 | a |
| 101 | b |
| 5 | c |
| 102 | d |
+-----+------+
然而在这个case中, 下一个可用的auto-increment值将会是105(并发是103), 因为四个auto-increment值是语句开始处理时分配的, 但是只有两个被使用. 这个结果是可以确定的, 无论是否并发执行"INSERT-like"语句.
innodb_autoinc_lock_mode = 2, 4条新插入的行数据将会是:
mysql> SELECT c1, c2 FROM t1 ORDER BY c2;
+-----+------+
| c1 | c2 |
+-----+------+
| 1 | a |
| x | b |
| 5 | c |
| y | d |
+-----+------+
x和y的值将会是唯一并且大于任何之前产生的值, x和y的值取决于并发执行的SQL语句产生的auto-increment值.
最后, 考虑下面的语句, 假如最新产生的序列值是4:
mysql> INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
对于任何级别的lock mode, 该语句将会产生duplicate-key错误 23000(Can't write; duplicate key in table), 因为5将会被自动分配给(NULL,'b')作为auto-increment值, 但是(5,'c')插入将会失败.
Modifying AUTO_INCREMENT column values in the middle of a sequence of
INSERT
statements在所有lock mode级别下, 在一序列
INSERT
语句中修改auto-increment值将会导致"Duplicate entry"错误.假如, 将行一个
UPDATE
语句修改auto-increment值为当前最大的auto-increment值还大, 随后INSERT
语句将会遇到自动产生的auto-increment值发生"Duplicate entry"错误, 如下例:mysql> CREATE TABLE t1 (
-> c1 INT NOT NULL AUTO_INCREMENT,
-> PRIMARY KEY (c1)
-> ) ENGINE = InnoDB; mysql> INSERT INTO t1 VALUES(0), (0), (3); mysql> SELECT c1 FROM t1;
+----+
| c1 |
+----+
| 1 |
| 2 |
| 3 |
+----+ mysql> UPDATE t1 SET c1 = 4 WHERE c1 = 1; mysql> SELECT c1 FROM t1;
+----+
| c1 |
+----+
| 2 |
| 3 |
| 4 |
+----+ mysql> INSERT INTO t1 VALUES(0);
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'
InnoDB AUTO_INCREMENT Counter Initialization
InnoDB如何初始化auto-increment计数器?
如果一个InnoDB表指定了一个auto-increment列, 在InnoDB数据字典中将会包含一个特殊的计数器, 叫做auto-increment counter
, 这个计数器仅仅存储在内存中.
mysqld server restart后会初始化auto-increment counter
计数器, 第一次向包含auto-increment列的表里插入数据时InnoDB会执行下面的语句:
初始化
SELECT MAX(ai_col) FROM table_name FOR UPDATE;
加步长
将得到的结果+1(默认)作为auto-increment计数器的起始值(auto_increment_increment参数可以指定步长)
如果表是空的, InnoDB使用1作为计数器的起始值(auto_increment_offset参数可以指定起始值)
如果SHOW TABLE STATUS
语句在auto-increment counter
计数器初始化前执行, InnoDB将会执行初始化, 但不会增加该值. 这个值将会存储着一辈后来的inserts
使用, 初始化使用简单的排他(exclusive-locking)锁读取表, 锁持续到事务结束.
在auto-increment counter
计数器被初始化后, 如果没有为auto-increment列特别指定值, 那么InnoDB将会增加计数器并且分配自增长值. 如果为auto-increment列特别指定了插入值, 并且插入值比当前计数器值大, 计数器将会被设置为指定的值.
在server运行期间InnoDB使用内存型的auto-increment counter
, 当server stopped并且restart, InnoDB在第一次插入表数据时为该表初始化计数器, 如之前讨论的那样.
server restart后同样会取消影响表级别的AUTO_INCREMENT = N
(CREATE TABLE
和ALTER TABLE
语句).
讨论
- INSERT INTO t1...SELECT ... FROM t该SQL导致故障, 想想为什么?
参考资料
http://dev.mysql.com/doc/refman/5.5/en/innodb-auto-increment-handling.html
http://dev.mysql.com/doc/refman/5.5/en/innodb-locks-set.html
[MySQL] AUTO_INCREMENT lock Handing in InnoDB的更多相关文章
- mysql中lock tables与unlock tables
官网:https://dev.mysql.com/doc/refman/5.0/en/lock-tables.html LOCK TABLES tbl_name [[AS] alias] lock_t ...
- [MySQL] gap lock/next-key lock浅析
当InnoDB在判断行锁是否冲突的时候, 除了最基本的IS/IX/S/X锁的冲突判断意外, InnoDB还将锁细分为如下几种子类型: record lock (RK) 记录锁, 仅仅锁住索引记录的一行 ...
- mysql metadata lock(一)
想必玩过mysql的人对Waiting for table metadata lock肯定不会陌生,一般都是进行alter操作时被堵住了,导致了我们在show processlist 时,看到线程的状 ...
- MySQL 全文搜索支持, mysql 5.6.4支持Innodb的全文检索和类memcache的nosql支持
背景:搞个个人博客的全文搜索得用like啥的,现在mysql版本号已经大于5.6.4了也就支持了innodb的全文搜索了,刚查了下目前版本号都到MySQL Community Server 5.6.1 ...
- MySQL中lock tables和unlock tables浅析
MySQL中lock tables和unlock tables浅析 在MySQL中提供了锁定表(lock tables)和解锁表(unlock tables)的语法功能,ORACLE与SQL Se ...
- MySQL存储引擎 - Myisam和Innodb
Mysql有两种存储引擎:InnoDB与Myisam,下表是两种引擎的简单对比 MyISAM InnoDB 构成上的区别: 每个MyISAM在磁盘上存储成三个文件.第一个 文件的名字以表的名字开始 ...
- [MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率
首先,介绍下关于InnoDB引擎存储格式的几个要点:1.InnoDB可以选择使用共享表空间或者是独立表空间方式,建议使用独立表空间,便于管理.维护.启用 innodb_file_per_table 选 ...
- mysql 流程函数 存储引擎 InnoDB简单特性
建表及插入数据语句: mysql> create table salary(userid int,salary decimal(9,2)); Query OK, 0 rows affected ...
- MySQL数据库之MyISAM与InnoDB的区别
MySQL数据库之MyISAM与InnoDB的区别 从以下几个方面: 1.存储结构 每个MyISAM在磁盘上存储成三个文件.第一个文件的名字以表的名字开始,扩展名指出文件类型. .frm文件存储表定义 ...
随机推荐
- 【uoj#164】[清华集训2015]V 线段树维护历史最值
题目描述 给你一个长度为 $n$ 的序列,支持五种操作: $1\ l\ r\ x$ :将 $[l,r]$ 内的数加上 $x$ :$2\ l\ r\ x$ :将 $[l,r]$ 内的数减去 $x$ ,并 ...
- week1day01 认识python 变量 数据类型 条件if语句
1.什么是python? Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.Python由Guido van Rossum于1989年底发明,第一个公开发行版发行于1991年.像Pe ...
- 【刷题】洛谷 P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- 【刷题】BZOJ 5248 [2018多省省队联测]一双木棋
Description 菲菲和牛牛在一块n行m列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手.棋局开始时,棋盘上没有任何棋子, 两人轮流在格子上落子,直到填满棋盘时结束.落子的规则是:一个格子可以落子 ...
- CF Playrix Codescapes Cup Problems Analysis
A 理清思路模拟 B 先对3个array排序,然后每次从某个array的头删数,可保证每个数必被处理1次,O(n log n) (set维护也行) C 分3类情况讨论,一种为: p1≤p2 & ...
- atom插件安装引发的nodejs和npm安装血案
最近在写前端网页,学习就要从高大上的地方开始,于是我打算装一个atom编辑器. 本来就是由github客户端的,再装个atom也算是配套了吧,其实本白也是蛮费心思的,技术不怎么地,什么神器都再努力地使 ...
- 【OpenCV】特征检测器 FeatureDetector
<SIFT原理与源码分析>系列文章索引:http://www.cnblogs.com/tianyalu/p/5467813.html OpenCV提供FeatureDetector实现特征 ...
- thinkphp常见问题
1.数据库查询中execute和query方法的区别 tp中execute()和query()方法都可以在参数里直接输入sql语句. 但是不同的是execute()通常用来执行insert或者upda ...
- ubuntu 16 server 安装lnmp所需依赖
安装 1.nginx build-essential libc6 libpcre3 libpcre3-dev libssl-dev zliblg zliblg-dev lab-base 依赖库: ap ...
- python 字符串前缀u, r, b小结
http://note.youdao.com/noteshare?id=a0da9c2d044d270fa8cb162b932c47e8