mysql锁机制

2025-03-13  本文已影响0人  _刘小c

MySQL 锁机制与事务控制

1. MySQL 锁分类

1.1 全局锁(Global Lock)

官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。

mysqldump --single-transaction -u root -p mydb > backup.sql

如果要全库只读,可以使用 set global readonly=true 的方式吗?确实 readonly 方式也可以让全库进入只读状态,但还是会建议用 FTWRL 方式,主要有两个原因:

1.2 表级锁(Table Lock)

MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)

在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是太大。

事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。

1.3 行级锁(Row Lock)(InnoDB 专属)


2. InnoDB 事务锁机制

2.1 间隙锁(Gap Lock)

2.2 提链锁(Next-Key Lock)

2.3 自增锁(AUTO-INC Lock)


3. 两阶段提交(2PC)

3.1 介绍

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议 。如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放

3.2 执行过程

  1. 准备阶段(Prepare):所有参与者预执行 SQL,记录 undo log,不提交。
  2. 提交阶段(Commit)
    • 所有参与者成功 → 事务提交。
    • 任一失败 → 事务回滚。

4. MySQL 死锁与死锁检测

4.1 死锁的常见原因

  1. 事务访问资源顺序不同(A 先锁 X,B 先锁 Y,导致循环等待)。
  2. 索引未命中(导致全表扫描,增加锁冲突)。
  3. Next-Key Lock 造成锁冲突(可重复读级别)。
  4. 长事务持有大量行锁(影响并发性能)。

4.2 MySQL 如何处理死锁?

当出现死锁以后,有两种策略:

你可以想象一下这个过程:每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。那如果是我们上面说到的所有事务都要更新同一行的场景呢?
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。

如何解决由热点行更新导致的性能问题呢?

问题的症结在于,死锁检测要耗费大量的 CPU 资源。

一种头痛医头的方法,就是如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。但是这种操作本身带有一定的风险,因为业务设计的时候一般不会把死锁当做一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是业务无损的。而关掉死锁检测意味着可能会出现大量的超时,这是业务有损的。

另一个思路是控制并发度。根据上面的分析,你会发现如果并发能够控制住,比如同一行同时最多只有 10 个线程在更新,那么死锁检测的成本很低,就不会出现这个问题。一个直接的想法就是,在客户端做并发控制。但是,你会很快发现这个方法不太可行,因为客户端很多。我见过一个应用,有 600 个客户端,这样即使每个客户端控制到只有 5 个并发线程,汇总到数据库服务端以后,峰值并发数也可能要达到 3000。

因此,这个并发控制要做在数据库服务端。如果你有中间件,可以考虑在中间件实现;如果你的团队有能修改 MySQL 源码的人,也可以做在 MySQL 里面。基本思路就是,对于相同行的更新,在进入引擎之前排队。这样在 InnoDB 内部就不会有大量的死锁检测工作了。

还有一个思路是考虑通过将一行改成逻辑上的多行来减少锁冲突

4.3 如何避免死锁?

最佳实践

  1. 统一事务访问顺序,避免循环等待。
  2. 优化索引,减少表扫描
  3. 减少事务持锁时间,及时提交
  4. 使用行锁代替表锁,降低锁粒度
  5. 避免大事务,拆分为小事务

5. 常见锁相关 SQL

5.1 查询锁信息

SHOW ENGINE INNODB STATUS;

5.2 查看等待事务

SELECT * FROM information_schema.innodb_trx;

5.3 终止死锁事务

KILL <thread_id>;

6. 总结

主题 关键点
全局锁 主要用于逻辑备份,InnoDB 推荐 --single-transaction
表级锁 影响整个表,适用于 MyISAM,包含 MDL 锁。
行级锁 InnoDB 专属,适用于高并发。
两阶段提交 适用于分布式事务,MySQL XA 事务支持。
死锁原因 资源访问顺序不同、索引未命中、Next-Key Lock 影响、大事务锁等待。
死锁检测 InnoDB 自动检测死锁,可能导致 CPU 过载,超时回滚可缓解。
如何避免死锁 事务顺序统一、优化索引、减少事务持锁时间、避免大事务。

🔹 在高并发环境下,优化事务逻辑和索引,合理设计锁策略,是防止死锁的关键!如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。但是,调整语句顺序并不能完全避免死锁。所以还引入了死锁和死锁检测的概念,以及提供了三个方案,来减少死锁对数据库的影响。减少死锁的主要方向,就是控制访问相同资源的并发事务量。 🚀

上一篇 下一篇

猜你喜欢

热点阅读