Mysql 中的锁
什么是“锁”?
- *锁(Lock)**是数据库为保证多用户并发访问数据时,数据的一致性与完整性而采取的一种机制。
就像你在一个文档上修改,防止别人同时修改冲突,给它上锁
数据库主要有两类锁:
分类 | 子类型 | 简要说明 |
---|---|---|
物理层锁 | 表锁、行锁、页锁 | 作用于数据“位置” |
逻辑层锁 | 乐观锁、悲观锁 | 作用于“逻辑业务”,一般由程序实现 |
✅ 1. 表锁(Table Lock)
- 整张表加锁,其他线程无法读写
- 粒度大,冲突概率高,MyISAM引擎用得多
LOCK TABLES user WRITE;
✅ 2. 行锁(Row Lock)
- 只锁住当前操作的行
- 粒度小,性能好,InnoDB默认支持
- 举例:
UPDATE user SET name = '张三' WHERE id = 1;
✅ 3. 乐观锁(Optimistic Lock)
- 假设没有冲突,不加锁
- 提交前检查“版本”或“时间戳”
- 冲突时提示失败
- 一般通过字段控制,如:
UPDATE table SET version = version + 1
WHERE id = 1 AND version = 3;
✅ 4. 悲观锁(Pessimistic Lock)
- 认为有冲突,加锁保护
- 每次读取/写入都锁住数据(阻塞其他操作)
- InnoDB默认是悲观锁模式,如:
SELECT * FROM user WHERE id = 1 FOR UPDATE;
🔁 四、UPDATE 语句的锁行为
情况分析(基于 InnoDB 引擎):
- 使用索引可确保行锁,否则可能锁整张表
SQL语句 | 锁类型 | 说明 |
---|---|---|
UPDATE … WHERE id=1 | 行锁 | 默认加排他锁(X锁),锁住符合条件的行 |
UPDATE … 没有索引 | 表锁(或间隙锁) | InnoDB 尝试行锁失败时退化为表锁或意外加范围锁 |
事务外操作 | 自动提交 + 加锁 | 没有事务会立即提交释放锁 |
加 FOR UPDATE | 显式加锁 | 同样是行锁,但常用于事务读取后锁定数据 |
为什么insert/update 不会锁整张表?
在使用 InnoDB 引擎 时,MySQL 默认采用的是 行级锁(Row Lock),这是它区别于 MyISAM 表锁的重要优势之一:
操作类型 | 加锁级别 | 说明 |
---|---|---|
INSERT | 行锁 / 无锁 | 通常不会锁已有数据,只是插入,几乎不会阻塞其他操作 |
UPDATE … WHERE id=1 | 行锁 | 会锁住满足条件的行(id=1),不会锁整张表 |
DELETE … WHERE id=1 | 行锁 | 同样只锁住目标行 |
⚠️ 但注意:
如果:
- 没有使用索引 或
- WHERE 语句无法命中索引,
那 MySQL 可能会退化为表锁或间隙锁,这是性能隐患!
✅ 二、如果我此时在执行 DDL 操作(比如 ALTER TABLE)会发生什么?
🔒 DDL 操作(如 ALTER TABLE)一定会加表锁!
操作 | 锁类型 | 说明 |
---|---|---|
ALTER TABLE … | 表锁 | 会阻塞该表的读写,直到结构变更完成 |
ADD/DROP COLUMN | 表锁 | 同上 |
CREATE INDEX(同步) | 表锁 | 会阻塞写入操作(可考虑在线索引) |
如果在执行:
ALTER TABLE user ADD COLUMN nickname VARCHAR(255);
此时其他连接中的以下操作会被 阻塞:
SELECT * FROM user; -- ❌ 阻塞
UPDATE user SET name='a'; -- ❌ 阻塞
INSERT INTO user ... -- ❌ 阻塞
🚫 在该 ALTER TABLE 执行期间:
- 任何对这张表的 读操作(SELECT * FROM user)都会被阻塞
- 任何对这张表的 写操作(UPDATE、INSERT、DELETE)也都会被阻塞
✅ 等ALTER TABLE完成后:
这些被阻塞的操作才会依次执行,或者超时失败(取决于客户端/连接池设置的超时时间)。
🔧 解决建议(生产环境):
- 避免高峰期执行 DDL 操作
- 使用在线DDL工具(如 Percona 的 pt-online-schema-change、GitHub 的 gh-ost),避免阻塞业务请求
DDL的排队
Data Definition Language
-
MySQL 的 DDL 操作本质上 需要排他访问表,也就是说:
要等到当前表上没有其他事务正在访问或占锁
🔁 举个例子:
时间 | 操作 | 描述 |
---|---|---|
T1 | 业务线程A执行 UPDATE user SET … WHERE id=1 并开启了事务,但没有提交 | 占用行锁 |
T2 | 你执行了 alembic upgrade,尝试 ALTER TABLE user ADD COLUMN xxx | MySQL 尝试加表锁,发现表上还有事务未提交 → 被阻塞,卡住了 |
T3 | 业务线程A事务迟迟没提交 | 你这边就一直挂着 |
误解 | 实际机制 |
---|---|
“DDL 最优先,其他操作应该等它” | ❌ 错!DDL 也要等现有事务释放表相关锁 |
“表被频繁更新,DDL 就会失败” | ✅ 是的,频繁操作造成的锁占用会阻塞 DDL |
“表结构改不动是因为 MySQL 不稳定” | ❌ 是因为 DDL 拿不到锁 |
✅ 你的 update 没写事务,为什么还是会“加锁”?
因为InnoDB 默认使用“自动提交模式”
也就是说,如果你没有显式写 BEGIN 或 START TRANSACTION,那么:
UPDATE user SET name = '张三' WHERE id = 1;
这条语句实际上会被 MySQL 当作:
START TRANSACTION;
UPDATE user SET name = '张三' WHERE id = 1;
COMMIT;
所以虽然你“看起来没用事务”,InnoDB 内部还是执行了一个隐式事务。
✅ 这个隐式事务行为的结果是:
UPDATE 时,会加 行锁(InnoDB 默认)
因为你没显式控制事务,所以 语句一执行完就自动提交,锁也就立刻释放了
因此,大多数情况下你看不到锁阻塞的现象