MySQL的锁机制是保证多事务并发场景下数据一致性的核心手段,不同的锁类型对应不同的作用范围和加锁规则,适用场景也有明显差异。

MySQL锁的分类
按作用范围分类
表锁是作用于整张表的锁,加锁速度快,开销小,但是并发度低,适合对整表做批量操作的场景。
行锁是作用于单行或者多行记录的锁,开销比表锁大,加锁速度慢,但是并发度高,是InnoDB引擎默认的锁粒度,适合高并发的点查或者范围更新场景。
按兼容性分类
共享锁也叫读锁,多个事务可以同时持有同一资源的共享锁,但是不能和排他锁共存,通常用于需要读取数据但是不希望被其他事务修改的场景。
排他锁也叫写锁,同一时间只能有一个事务持有同一资源的排他锁,其他事务既不能加共享锁也不能加排他锁,适合需要修改数据的场景。
常见锁的使用场景
下面通过具体的代码示例说明不同锁的使用方式:
表锁的使用
MyISAM引擎默认使用表锁,手动加表锁的语法如下:
-- 给user表加读锁 LOCK TABLES user READ; -- 查询数据 SELECT * FROM user WHERE id = 1; -- 解锁 UNLOCK TABLES; -- 给user表加写锁 LOCK TABLES user WRITE; -- 更新数据 UPDATE user SET name = 'test' WHERE id = 1; -- 解锁 UNLOCK TABLES;
这种锁适合对整张表做全量导出、批量更新等不需要高并发的场景,比如每天凌晨批量同步用户统计数据的时候,就可以用表锁避免其他事务干扰。
行锁的使用
InnoDB引擎支持行锁,需要在事务中使用,共享锁用LOCK IN SHARE MODE,排他锁用FOR UPDATE:
-- 开启事务 START TRANSACTION; -- 加共享锁查询 SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 其他事务可以加共享锁查询,但是不能加排他锁修改,直到当前事务提交 COMMIT; -- 开启事务 START TRANSACTION; -- 加排他锁查询 SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 其他事务既不能加共享锁也不能加排他锁操作这条记录,直到当前事务提交 UPDATE user SET age = 20 WHERE id = 1; COMMIT;
行锁适合高并发的订单修改、账户余额更新这类场景,比如用户下单的时候,对商品库存行加排他锁,避免超卖问题。
不同事务隔离级别下的锁表现
MySQL的事务隔离级别也会影响锁的行为,不同隔离级别对应的锁场景如下:
| 隔离级别 | 锁表现 | 适用场景 |
|---|---|---|
| 读未提交 | 几乎不加锁,会出现脏读、幻读问题 | 对数据一致性要求极低的统计场景 |
| 读已提交 | 普通查询不加锁,更新操作加行锁,会出现幻读 | 高并发、允许偶尔幻读的普通业务场景 |
| 可重复读 | 普通查询不加锁,更新操作加行锁+间隙锁,避免幻读 | 大多数需要保证数据一致性的业务场景 |
| 串行化 | 所有查询加共享锁,更新加排他锁,完全串行执行 | 对数据一致性要求极高、并发量很低的场景 |
锁使用注意事项
- 尽量缩小锁的范围,避免长事务持有锁的时间过长,否则容易导致锁等待甚至死锁。
- 更新数据的时候尽量用主键或者唯一索引作为条件,避免行锁升级为表锁。
- 如果业务需要读取数据后马上修改,建议直接加排他锁,避免先加共享锁再升级为排他锁的时候出现死锁。
合理选择锁类型和事务隔离级别,才能在保证数据一致性的前提下最大化MySQL的并发性能。