`

【转】MySQL innodb表自增主键问题

阅读更多

背景:

      自增长是一个很常见的数据属性,在MySQL中大家都很愿意让自增长属性的字段当一个主键。特别是InnoDB,因为InnoDB的聚集索引的特性,使用自增长属性的字段当主键性能更好,这里要说明下自增主键需要注意的几个事项。

问题一:表锁

      在MySQL5.1.22之前,InnoDB自增值是通过其本身的自增长计数器来获取值,该实现方式是通过表锁机制来完成的(AUTO-INC LOCKING)。锁不是在每次事务完成后释放,而是在完成对自增长值插入的SQL语句后释放,要等待其释放才能进行后续操作。比如说当表里有一个auto_increment字段的时候,innoDB会在内存里保存一个计数器用来记录auto_increment的值,当插入一个新行数据时,就会用一个表锁来锁住这个计数器,直到插入结束。如果大量的并发插入,表锁会引起SQL堵塞。

      在5.1.22之后,InnoDB为了解决自增主键锁表的问题,引入了参数innodb_autoinc_lock_mode,该实现方式是通过轻量级互斥量的增长机制完成的。它是专门用来在使用auto_increment的情况下调整锁策略的,目前有三种选择:

插入类型说明:

INSERT-LIKE:指所有的插入语句,比如 INSERT、REPLACE、INSERT…SELECT、REPLACE…SELECT,LOAD DATA等
Simple inserts:指在插入前就能确定插入行数的语句,包括INSERT、REPLACE,不包含INSERT…ON DUPLICATE KEY UPDATE这类语句。
Bulk inserts:指在插入前不能确定得到插入行的语句。如INSERT…SELECT,REPLACE…SELECT,LOAD DATA.
Mixed-mode inserts:指其中一部分是自增长的,有一部分是确定的。

 0:通过表锁的方式进行,也就是所有类型的insert都用AUTO-inc locking。

1:默认值,对于simple insert 自增长值的产生使用互斥量对内存中的计数器进行累加操作,对于bulk insert 则还是使用表锁的方式进行。

2:对所有的insert-like 自增长值的产生使用互斥量机制完成,性能最高,并发插入可能导致自增值不连续,可能会导致Statement 的 Replication 出现不一致,使用该模式,需要用 Row Replication的模式。

      在mysql5.1.22之前,mysql的INSERT-LIKE语句会在执行整个语句的过程中使用一个AUTO-INC锁将表锁住,直到整个语句结束(而不是事务结束)。因此在使用INSERT…SELECT、INSERT…values(…),values(…)时,LOAD DATA等耗费时间较长的操作时,会将整个表锁住,而阻塞其他的insert-like,update等语句。推荐使用程序将这些语句分成多条语句,一一插入,减少单一时间的锁表时间。

解决:

通过参数innodb_autoinc_lock_mode =1/2解决,并用simple inserts 模式插入。

问题二:自增主键不连续

5.1.22后 默认:innodb_autoinc_lock_mode = 
直接通过分析语句,获得要插入的数量,然后一次性分配足够的auto_increment id,只会将整个分配的过程锁住。

 
root@localhost : test 04:23:28>show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 1     |
+--------------------------+-------+
1 row in set (0.00 sec)

root@localhost : test 04:23:31>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk;
Query OK, 0 rows affected (0.16 sec)

root@localhost : test 04:23:35>insert into tmp_auto_inc(talkid) select talkId from talk_dialog limit 10;
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

root@localhost : test 04:23:39>show create table tmp_auto_inc\G;
*************************** 1. row ***************************
       Table: tmp_auto_inc
Create Table: CREATE TABLE `tmp_auto_inc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `talkid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
  

插入10条记录,但表的AUTO_INCREMENT=16,再插入一条的时候,表的自增id已经是不连续了。

原因:

      参数innodb_autoinc_lock_mode = 1时,每次会“预申请”多余的id(handler.cc:compute_next_insert_id),而insert执行完成后,会特别将这些预留的id空出,就是特意将预申请后的当前最大id回写到表中(dict0dict.c:dict_table_autoinc_update_if_greater)。

      这个预留的策略是“不够时多申请几个”, 实际执行中是分步申请。至于申请几个,是由当时“已经插入了几条数据N”决定的。当auto_increment_offset=1时,预申请的个数是 N-1。

      所以会发现:插入只有1行时,你看不到这个现象,并不预申请。而当有N>1行时,则需要。多申请的数目为N-1,因此执行后的自增值为:1+N+(N-1)。测试中为10行,则:1+10+9 =20,和 16不一致?原因是:当插入8行的时候,表的AUTO_INCREMENT已经是16了,所以插入10行时,id已经在第8行时预留了,所以直接使用,自增值仍为16。所以当插入8行的时候,多申请了7个id,即:9,10,11,12,13,14,15。按照例子中的方法插入8~15行,表的AUTO_INCREMENT始终是16

验证:

插入16行:猜测 预申请的id:1+16+(16-1)= 32,即:AUTO_INCREMENT=32

root@localhost : test 04:55:45>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk;
Query OK, 0 rows affected (0.17 sec)

root@localhost : test 04:55:48>insert into tmp_auto_inc(talkid) select talkId from sns_talk_dialog limit 16;
Query OK, 16 rows affected (0.00 sec)
Records: 16  Duplicates: 0  Warnings: 0

root@localhost : test 04:55:50>show create table tmp_auto_inc\G;
*************************** 1. row ***************************
       Table: tmp_auto_inc
Create Table: CREATE TABLE `tmp_auto_inc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `talkid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
  

和猜测的一样,自增id到了32。所以当插入16行的时候,多申请了17,18,19...,31 。

所以导致ID不连续的原因是因为innodb_autoinc_lock_mode = 1时,会多申请id。好处是:一次性分配足够的auto_increment id,只会将整个分配的过程锁住。

5.1.22前 默认:innodb_autoinc_lock_mode = 0

root@localhost : test 04:25:12>show variables like 'innodb_autoinc_lock_mode';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_autoinc_lock_mode | 0     |
+--------------------------+-------+
1 row in set (0.00 sec)

root@localhost : test 04:25:15>create table tmp_auto_inc(id int auto_increment primary key,talkid int)engine = innodb default charset gbk;
Query OK, 0 rows affected (0.17 sec)

root@localhost : test 04:25:17>insert into tmp_auto_inc(talkid) select talkId from talk_dialog limit 10;
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

root@localhost : test 04:25:21>show create table tmp_auto_inc\G;
*************************** 1. row ***************************
       Table: tmp_auto_inc
Create Table: CREATE TABLE `tmp_auto_inc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `talkid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
  

插入10条记录,但表的AUTO_INCREMENT=11,再插入一条的时候,表的自增id还是连续的。

innodb_autoinc_lock_mode = 2 和 innodb_autoinc_lock_mode = 1 的测试情况一样。但该模式下是来一个分配一个,而不会锁表,只会锁住分配id的过程,和1的区别在于,不会预分配多个,这种方式并发性最高。但是在replication中当binlog_format为statement-based时存在问题

解决:

尽量让主键ID没有业务意义,或则使用simple inserts模式插入。

结论:

当innodb_autoinc_lock_mode为0时候, 自增id都会连续,但是会出现表锁的情况,解决该问题可以把innodb_autoinc_lock_mode 设置为1,甚至是2。会提高性能,但是会在一定的条件下导致自增id不连续。

总结:

通过上面2个问题的说明,自增主键会产生表锁,从而引发问题;自增主键有业务意义,不连续的主键导致主从主键不一致到出现问题。对于simple inserts 的插入类型,上面的问题都不会出现。对于Bulk inserts的插入类型,会出现上述的问题。

更多信息:

http://dinglin.iteye.com/blog/1279536

http://dev.mysql.com/doc/refman/5.5/en/innodb-auto-increment-handling.html 

http://blog.chinaunix.net/uid-9950859-id-181376.html 

分享到:
评论

相关推荐

    MySQL8自增主键变化.doc

    然后向自增主键表中添加了 4 条记录,表中的四条添加的记录的 id 字段值就分别为: 1、2、3、4。接下来,将表中的 id 为 4 的字段删除,然后,继续在表中添加一条记录,执行之后我们可以发现,此时自增主键的 ID 结果...

    MySQL8新特性:自增主键的持久化详解

    MySQL 8.0引入的新特性之一是自增主键的持久化,这主要是为了解决一个长期存在的问题,即自增主键计数器在数据库重启后无法保持其状态。在旧版本的MySQL中,自增主键的值是通过InnoDB数据字典中的内存计数器来管理的...

    MySQL自增主键删除后重复问题

     设置一张MySQL表,表里有一个自增主键ID,往表里插入数据,假如插入数据之后表后一行的ID是100,我先删除这条ID为100的记录,然后重新启动服务器,按理说如果再往这个表里插入新的记录,新纪录的ID将为101,对吧...

    自增主键为什么不是连续的?.pdf

    然而,自增主键并不总是连续的,这主要与数据库管理系统(如MySQL)如何处理自增值以及自增值的存储和修改机制有关。 首先,自增主键的连续性不能被视为一种保证。尽管自增主键通常按递增顺序增长,但存在多种情况...

    使用prometheus统计MySQL自增主键的剩余可用百分比

    在这个场景中,我们关注的是如何使用Prometheus来统计MySQL自增主键的剩余可用百分比,以预防生产环境中可能出现的主键溢出问题。 MySQL自增主键是数据库表中一种常见的标识符,它会自动递增以确保每个记录的唯一性...

    mysql自增字段重排 mysql删除表后自增字段从1开始.pdf

    理解这些知识点对于管理和维护MySQL数据库中的自增字段至关重要,可以帮助避免潜在的问题并优化数据存储效率。根据实际需求选择合适的存储引擎,以及正确处理自增字段的删除和初始化,能有效提升数据库的性能和管理...

    MySQL Innodb 索引原理详解

    主键索引提供了最快的访问速度,因为主键通常是自增ID或类似的数据类型,能够均匀分布。 ##### 2.2 非主键索引 非主键索引通常指的是次级索引或者辅助索引,它们同样采用B+树结构。与主键索引不同,非主键索引的...

    mysql自增字段重排 mysql删除表后自增字段从1开始.docx

    MySQL中的自增字段是数据库设计中常用的一种特性,主要用于生成唯一的序列号,通常用于主键。自增字段的默认行为是每次插入新记录时自动增加一个整数值,从1开始并递增。然而,当删除记录或有特殊操作时,自增字段的...

    Oracle的表结构转成Mysql的表结构

    ### Oracle的表结构转成MySQL的表结构 #### 功能概述 本文介绍了一种将Oracle数据库中的表结构转换为MySQL数据库表结构的方法。通过编写一个PL/SQL函数`fnc_table_to_mysql`来实现这一目标。该函数可以接受四个参数...

    mysql innodb引擎 知识点总结

    7. **自增ID**:InnoDB表有一个特殊的内置列,即自动增长列(通常为主键),每次插入新行时,该列的值会自动递增。 8. **redo log**:redo日志用于记录事务中的更改,确保在系统崩溃时能恢复到一致性状态。它是...

    MySQL55道面试题及答案

    在 MySQL 中,自增主键的最大 ID 会被记录在数据文件中,如果表类型是 MyISAM,那么重启 MySQL 之后,自增主键的最大 ID 不会丢失;如果表类型是 InnoDB,那么自增主键的最大 ID 只记录在内存中,重启数据库或对表...

    MySQL的自增ID(主键) 用完了的解决方法

    如果在创建表时未显式定义主键,InnoDB存储引擎会自动创建一个6字节的不可见主键`row_id`,基于`BIGINT UNSIGNED`,但实际只使用了48位,这可能导致在特定情况下出现主键冲突。 4. **主键冲突处理**: - 当全局`...

    MySQL面试题及答案.pdf

    如果表类型是 MyISAM,重启 MySQL 后,自增主键的最大 ID 不会丢失;如果表类型是 InnoDB,重启数据库后,自增主键的最大 ID 会丢失。 2. MySQL 的技术特点:MySQL 是一个客户端或服务器系统,支持多线程 SQL ...

    MySQL经典面试题55道

    知识点:MySQL表的自增主键、MyISAM和InnoDB存储引擎的差异。 2. MySQL的技术特点:MySQL是一种客户端/服务器系统,包括多线程SQL服务器、不同后端、广泛的应用程序编程接口和管理工具。 知识点:MySQL的技术架构...

    mysql的插入问题 怎么获得自动增长的ID

    ### MySQL的插入问题:如何获取自动增长的ID 在MySQL中,经常需要用到自动增长的ID字段作为表的主键,特别是在频繁进行数据插入操作时。本文将深入探讨以下几个方面: 1. **理解自动增长ID的工作原理** 2. **在...

    MySQL55题答案.pdf

    在MySQL中,有MyISAM和InnoDB两种存储引擎处理自增主键的方式不同。如果表是MyISAM,自增主键的最大ID存储在数据文件中,即使删除记录并重启数据库,下一条插入的记录ID仍然是18。而对于InnoDB,自增主键的最大值...

    MySQL大表性能优化方案 和 MySQL高性能表设计规范

    4. **自增主键**:使用整数类型的自增主键,有利于索引效率和数据插入。 5. **避免NULL**:尽量避免使用NULL,NULL值会增加索引和查询复杂性。 6. **外键约束**:合理设置外键约束,确保数据完整性,但过多的外键...

Global site tag (gtag.js) - Google Analytics