mysql自增锁原理_MySQL AutoIncrement--自增锁模式

本文解析了MySQL自增锁机制的发展历程,从传统的表锁到轻量级互斥锁的转变,以及不同模式下的自增锁行为。讨论了自增锁模式参数innodb_autoinc_lock_mode的作用及设置建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自增锁模式

在MYSQL 5.1.22版本前,自增列使用AUTO_INC Locking方式来实现,即采用一种特殊的表锁机制来保证并发插入下自增操作依然是串行操作,为提高插入效率,该锁会在插入语句完成后立即释放,而不是插入语句所在事务提交时释放。该设计并发性能太差,尤其在大批量数据在一条语句中插入时(INSERT SELECT ), 会导致该语句长时间持有这个“表锁”,从而阻塞其他事务的插入操作。

在MYSQL 5.1.22版本开始,InnoDB存储引使用一种轻量级互斥锁(Mutex)来控制自增列增长,并提供innodb_autoinc_lock_mode参数来控制。

自增长方式可分为下面四类:

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

innodb_autoinc_lock_mode参数取值

1.innodb_autoinc_lock_mode=0 传统锁定模式, 5.1.22之前的方式,也就是所有INSERT-LIKE操作都用AUTO-inc locking。2.innodb_autoinc_lock_mode=1 连续锁定模式,这个参数是5.1.22之后出现的也是之后的默认值,对于SIMPLE INSERT,使用轻量级互斥锁,对于BULK INSERT,使用AUTO-inc locking。3.innodb_autoinc_lock_mode=2 交错锁定模式,指不管什么情况都使用轻量级互斥的锁,效率最高,但是复制只能使用row-basereplication,因为statement-base replication会出现问题。

当innodb_autoinc_lock_mode=1时,"Simple insert"操作能在插入前知道插入的记录数量,因此无需在整个插入操作过程中持有表级别的AUTO-INC锁,MySQL通过轻量级互斥锁来控制INSERT操作获取自增值的过程,并在INSERT操作获取到自增值后快速释放互斥锁,通过降低锁颗粒度和锁持续周期,实现"Simple insert"操作并发执行。当其他事物对表持有AUTO-INC锁时,"Simple insert"操作也会升级使用AUTO-INC锁并被阻塞。

当innodb_autoinc_lock_mode=1时,在语句复制格式下(BINLOG_FORMAT=STATEMENT),BINLOG中没有记录主库执行过程中获取到的所有自增值及其对应行的信息,要保证"Bulk insert"操作主从复制数据一致就必须保证语句在主库和从库执行时获取到相同自增值,而因此只能通过控制“获取连续自增值”的方式来实现,同时为避免受其他事务插入操作影响,就必须在表级别加锁且保证持有锁至语句结束。

在行复制格式下(BINLOG_FORMAT=ROW),主库BINLOG中保存有记录的所有列信息包括自增列值,因此无需通过AUTO-INC锁来保证主从数据一致。

在MySQL 8.0版本前,参数BINLOG_FORMAT的默认值为STATEMENT,参数innodb_autoinc_lock_mode的默认值为1。

在MySQL 8.0版本后,参数BINLOG_FORMAT的默认值被调整为ROW格式,参数innodb_autoinc_lock_mode的默认值为2。

自增值间隙

在MySQL中,获取自增值的操作是非事务性,获取自增值的操作产生的锁在语句执行过程中或执行完成里便被释放而不会持续到事务提交和回滚,获取到自增值也不会随事务回滚而回滚,因此不能依赖MySQL自增列来实现表中列值连续无间隙。

表中自增列作为代理键,只能用来标识和定位记录,而不应该承载业务逻辑,因此也不建议对自增列值进行显式更新。

AUTO_INC锁导致的死锁

在业务繁忙的表上通过pt-osc进行DDL操作变更时,可能会出现死锁,死锁场景如下:

pt-osc工具执行对tb001执行DDL变更操作,创建临时表_tb001_new来执行变更并拷贝数据,并在tb001上创建增删改三种触发器。

步骤1、事务1执行业务操作对表tb001进行数据更新,对更新记录加排他锁(X)

步骤2、pt-osc的拷贝线程拷贝数据,事务2执行INSERT INTO _tb001_new SELECT FROM tb001 WHERE ... LOCK IN SHARE MODE的操作,先执行INSERT INTO操作申请_tb001_new表上AUTO-INC锁,申请成功

步骤3、事务1更新操作触发表tb0011上update触发器执行中执行REPLACE INTO操作,向表_tb001_new更新插入数据,申请_tb001_new表上AUTO-INC锁,由于事务2持有_tb001_new表上AUTO-INC锁,因此事务1被阻塞。

步骤4、事务2获取_tb001_new表上AUTO-INC锁后,再执行SELECT FROM tb001 WHERE ... LOCK IN SHARE MODE操作,申请某个范围内所有记录上的共享锁(S),由于事务1持有其中一条或多条记录的排他锁(X),导致事务2被阻塞。

步骤5、事务2被阻塞,触发死锁检测,发现事务1和事务2相互等待锁资源形成死锁,挑选事务1或事务2进行事务回滚。

上述死锁解决办法:

1、在行复制格式下(BINLOG_FORMAT=ROW),可以考虑调整参数innodb_autoinc_lock_mode的值为2(非动态参数,需重启服务)

2、通过降低锁冲突概率来降低死锁发生概率:

1、在业务低峰期进行DDL变更操作2、优化业务SQL降低单个事务修改记录数3、调整pt-osc单次拷贝操作的数据量

参考资料:

原文:https://www.cnblogs.com/gaogao67/p/11123772.html

<think>我们正在讨论MyBatis-Plus中的@TableId注解与MySQL建表语句中AUTO_INCREMENT的关系。用户的问题:在使用MyBatis-Plus的@TableId注解实现自主键时,是否还需要在MySQL的建表语句中添加AUTO_INCREMENT?关键点:1.MyBatis-Plus的@TableId注解用于标识实体类的主键字段,并可以通过设置type来指定主键生成策略。其中,IdType.AUTO表示数据库ID自(依赖于数据库的自机制)。2.MySQL中的AUTO_INCREMENT是数据库层面的自设置,它使得插入数据时主键字段自动递。结论:是的,即使使用了@TableId(type=IdType.AUTO),也必须在MySQL的建表语句中为主键字段指定AUTO_INCREMENT。原因:-MyBatis-Plus的IdType.AUTO策略仅仅表示在插入数据时,MyBatis-Plus不会为主键生成值,而是期望数据库生成自值。-如果建表语句中没有指定AUTO_INCREMENT,那么数据库不会自动生成主键值,导致插入时可能因为主键为NULL(或重复)而失败。因此,两者需要配合使用:-数据库表定义:主键字段必须设置为AUTO_INCREMENT,以确保数据库能自动生成递的值。-实体类:使用@TableId(type=IdType.AUTO)注解,告诉MyBatis-Plus这个主键的值由数据库自动生成。示例:1.数据库建表语句(必须包含AUTO_INCREMENT):```sqlCREATETABLEuser(idINTNOTNULLAUTO_INCREMENTCOMMENT'用户ID',nameVARCHAR(50)COMMENT'用户姓名',ageINTCOMMENT'用户年龄',PRIMARYKEY(id));```2.实体类定义:```javapublicclassUser{@TableId(type=IdType.AUTO)privateLongid;privateStringname;privateIntegerage;//省略getter和setter}```注意:如果不指定AUTO_INCREMENT,即使使用了@TableId(type=IdType.AUTO),数据库也不会自动生成主键值,插入操作会报错(例如主键不能为NULL)。引用说明:我们参考了用户提供的引用[2]中的建表语句,其中明确使用了AUTO_INCREMENT,这符合数据库自主键的标准做法。因此,回答用户的问题:您仍然需要在MySQL的建表语句中指定AUTO_INCREMENT。相关问题:1.MyBatis-Plus中除了AUTO,还有哪些主键生成策略?2.如何在MyBatis-Plus中配置全局的主键策略?3.如果数据库表没有设置自,但MyBatis-Plus配置了IdType.AUTO,会发生什么?</think>### MyBatis-Plus 的 @TableId 注解与 MySQL AUTO_INCREMENT 的关系 **结论:** 即使在使用 MyBatis-Plus 的 `@TableId(type = IdType.AUTO)` 注解时,**仍然需要在 MySQL 建表语句中明确指定 `AUTO_INCREMENT`**。这两个配置是相互协作而非替代关系。 --- ### 原因分析 1. **职责分工不同** - **MySQL 的 `AUTO_INCREMENT`** 是数据库层的自机制,负责实际生成主键值。若不指定,数据库不会自动生成递主键[^1]。 ```sql -- 必须显式声明 AUTO_INCREMENT CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -- 关键配置 name VARCHAR(50), age INT ); ``` - **MyBatis-Plus 的 `@TableId`** 是应用层配置,仅指示框架“主键值由数据库生成”,并不会自动创建数据库自逻辑[^2]。 ```java public class User { @TableId(type = IdType.AUTO) // 仅声明依赖数据库自 private Long id; private String name; private Integer age; } ``` 2. **不配置的后果** 若建表时省略 `AUTO_INCREMENT`,插入数据时会出现以下错误: - **主键冲突**(重复值) - **非空约束报错**(主键为 `NULL`) --- ### 完整协作流程 1. **数据库配置** 建表时为主键列添加 `AUTO_INCREMENT`,确保数据库生成唯一递值。 ```sql CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -- 数据库自 name VARCHAR(50), age INT ); ``` 2. **MyBatis-Plus 配置** 实体类通过注解声明使用数据库自策略: ```java public class User { @TableId(type = IdType.AUTO) // 通知框架不生成主键值 private Long id; // 其他字段... } ``` 3. **插入操作** 框架自动忽略主键字段,由数据库填充值: ```java User user = new User(); user.setName("John"); user.setAge(30); userMapper.insert(user); // 插入后 user.getId() 获取数据库生成的值 ``` --- ### 常见误区解答 ❌ **误区:** “`@TableId(type = IdType.AUTO)` 会自动创建自列” ✅ **正解:** 该注解仅改变 MyBatis-Plus 的行为(不设置主键值),**数据库自仍需显式定义**。 --- ### 引用说明 [^1]: MySQL 的自主键必须通过 `AUTO_INCREMENT` 显式定义,否则无法自动生成唯一值。 [^2]: MyBatis-Plus 的 `IdType.AUTO` 是一个标记性配置,依赖数据库底层自机制实现功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值