MySQL 锁机制详解

MySQL 锁机制详解

MySQL 锁是并发控制的核心,搞懂锁,才能避免死锁和性能问题。

1. 表锁 vs 行锁

表锁:锁整个表,开销小,并发低。

  • MyISAM 用的就是表锁
  • 适合读多写少的场景

行锁:锁单行数据,开销大,并发高。

  • InnoDB 用的就是行锁
  • 适合写多的场景

2. 共享锁 vs 排他锁

共享锁(S锁):读锁,多个读不互斥。

SELECT ... LOCK IN SHARE MODE

排他锁(X锁):写锁,一个写独占。

SELECT ... FOR UPDATE

3. 间隙锁

InnoDB 用间隙锁防止幻读。锁的是一个范围,不是具体行。

比如你查 id > 10 AND id < 20,间隙锁会把这个区间锁住,别人插不进来。

4. 死锁

两个事务互相等对方的锁,都不放,就死锁了。

避免死锁的方法:

  • 按相同顺序访问表
  • 事务尽量小,快速提交
  • 降低隔离级别
  • 加索引,减少行锁范围

5. 查看锁状态

-- 查看当前锁
SHOW OPEN TABLES WHERE In_use > 0;

-- 查看 InnoDB 锁
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

-- 查看锁等待
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

6. 行锁什么时候会变成表锁

行锁失效就会升级成表锁:

  • 没用索引
  • 索引失效
  • 全表扫描

所以加索引不仅是为了查得快,也是为了锁得准。

总结

MySQL 锁的核心:

  • InnoDB 默认行锁,MyISAM 是表锁
  • 共享锁读、排他锁写
  • 间隙锁防幻读
  • 死锁要避免:顺序访问、小事务、加索引

搞懂这些,数据库并发问题基本能解决一半。


emer 发布于  2026-10-4 10:46 

MySQL 事务与锁:理解隔离级别和死锁的实战指南

前言

事务和锁是 MySQL 最核心也最容易出问题的部分。很多人写了很多年 SQL,却还是搞不清隔离级别和死锁。本文用最直白的方式讲清楚。

一、事务的 ACID

事务就是一组操作,要么全部成功,要么全部失败。四个特性:

  • A 原子性:一组操作是一个整体,不能拆分
  • C 一致性:事务前后,数据库从一个一致状态到另一个一致状态
  • I 隔离性:多个事务之间互不干扰
  • D 持久性:事务提交后,数据就永久保存了

二、并发问题

多个事务同时操作数据,会出什么问题?

1. 脏读

事务 A 读到了事务 B 还没提交的数据。

事务 A:修改了余额为 1000,但还没提交
事务 B:读到了余额 1000
事务 A:回滚了,余额变回 500
事务 B:拿着 1000 的错误数据继续操作

2. 不可重复读

事务 A 两次读同一行数据,结果不一样。

事务 A:第一次读余额是 500
事务 B:修改了余额为 1000,提交了
事务 A:第二次读余额是 1000

3. 幻读

事务 A 两次查询,结果集的行数不一样。

事务 A:第一次查询 age > 20 的用户,有 10 条
事务 B:插入了一条 age = 25 的用户,提交了
事务 A:第二次查询 age > 20 的用户,有 11 条

三、四种隔离级别

MySQL 用隔离级别来解决这些并发问题:

隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED) 会 会 会
读提交(READ COMMITTED) 不会 会 会
可重复读(REPEATABLE READ) 不会 不会 会(InnoDB 解决了)
串行化(SERIALIZABLE) 不会 不会 不会

MySQL 默认是可重复读(REPEATABLE READ),InnoDB 在这个级别下用间隙锁解决了幻读。

查看当前隔离级别

SELECT @@tx_isolation;

修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

四、锁的类型

1. 共享锁(S 锁)

读锁,多个事务可以同时持有。

SELECT ... LOCK IN SHARE MODE;

2. 排他锁(X 锁)

写锁,只有一个事务能持有。

SELECT ... FOR UPDATE;

3. 表锁 vs 行锁

  • 表锁:锁住整张表,开销小,但并发低
  • 行锁:锁住一行数据,开销大,但并发高

五、死锁

什么是死锁

两个事务互相等待对方释放锁。

事务 A:锁住了 id = 1,等待 id = 2
事务 B:锁住了 id = 2,等待 id = 1

两个事务都卡住了,谁也不让谁。

怎么避免死锁

  1. 按相同顺序访问表和行:所有事务都按 id 从小到大操作
  2. 事务尽量短:事务越长,锁持有时间越长
  3. 降低隔离级别:隔离级别越低,锁越少
  4. 加索引:没有索引会走表锁,更容易死锁

查看死锁

-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS;

六、实战:转账

-- 开启事务
BEGIN;

-- 扣款(加行锁)
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 检查余额
SELECT balance FROM account WHERE id = 1;

-- 如果余额不够,回滚
-- ROLLBACK;

-- 加钱
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 提交
COMMIT;

七、常见坑

  1. 事务太长:锁持有时间太长,容易死锁
  2. 没有索引:行锁变表锁,并发暴跌
  3. 隔离级别太高:用串行化,性能很差
  4. 忘记提交:事务一直开着,锁一直占着

总结

MySQL 事务与锁的核心思路:

  1. 事务保证 ACID,一组操作要么全成功要么全失败
  2. 隔离级别越高越安全,但性能越差
  3. MySQL 默认可重复读,InnoDB 解决了幻读
  4. 行锁比表锁并发高,但一定要有索引
  5. 按相同顺序操作,能避免大部分死锁

搞懂这些,你对 MySQL 的理解就超过 80% 的开发者了。


emer 发布于  2026-10-4 10:11