mysql的锁机制按照锁粒度划分,主要分为表锁和行锁两类,两者的核心差异体现在作用范围、开销、并发能力等多个方面,不同存储引擎对这两类锁的支持情况也存在区别。
mysql表锁和行锁的核心区别
表锁和行锁最根本的差异是锁的作用范围不同,这也导致了两者在其他特性上的诸多不同,具体对比如下:
| 对比维度 | 表锁 | 行锁 |
|---|---|---|
| 作用范围 | 锁定整张表,表内所有数据行都会被限制访问 | 只锁定操作涉及的单条或多条数据行,其他行不受影响 |
| 加锁开销 | 开销小,加锁速度快 | 开销大,加锁速度慢,需要维护锁的持有状态 |
| 死锁概率 | 不会出现死锁 | 高并发场景下可能出现死锁 |
| 并发性能 | 并发度低,同一时间只能有一个会话对表进行写操作 | 并发度高,不同会话操作不同行时互不干扰 |
| 支持引擎 | MyISAM、InnoDB都支持 | 仅InnoDB支持 |
表锁的使用场景与示例
表锁更适合以查询为主、写操作较少的场景,或者需要批量操作整张表的情况,MyISAM引擎默认就会给写操作加表锁。
表锁的加锁语法
手动给表加读锁和写锁的语法如下:
-- 给user表加读锁,当前会话和其他会话都只能读该表,当前会话写该表会报错,其他会话写会阻塞 LOCK TABLES user READ; -- 给user表加写锁,当前会话可以对表进行读写,其他会话的读写操作都会阻塞 LOCK TABLES user WRITE; -- 释放所有表锁 UNLOCK TABLES;
表锁的特点说明
读锁是共享锁,多个会话可以同时持有同一张表的读锁,不会互相阻塞;写锁是排他锁,同一时间只能有一个会话持有表的写锁,且写锁优先级高于读锁,如果有会话在等待写锁,新的读锁请求会被阻塞直到写锁释放。
行锁的使用场景与示例
行锁更适合写操作频繁、高并发的场景,InnoDB引擎默认使用行锁,行锁是基于索引实现的,如果操作没有命中索引,行锁会升级为表锁。
行锁的加锁语法
InnoDB的行锁通常在事务中自动加锁,也可以通过FOR UPDATE手动加排他行锁:
-- 开启事务 START TRANSACTION; -- 查询id为1的用户数据并加排他行锁,其他事务无法修改这条数据直到当前事务提交 SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 提交事务,释放行锁 COMMIT;
行锁的注意事项
- 行锁必须基于索引才能生效,如果WHERE条件没有使用索引,InnoDB会进行全表扫描,此时行锁会升级为表锁,失去行锁的高并发优势。
- 行锁的加锁和解锁是在事务提交时完成的,事务越长,行锁持有时间越久,越容易出现锁冲突和死锁。
- InnoDB还支持意向锁,是表级锁,用来标识事务即将对表中的行加锁,避免表锁和行锁的冲突检查开销。
如何选择表锁和行锁
实际业务中可以根据以下原则选择:
- 如果业务以大量查询为主,写操作很少,或者使用的是MyISAM引擎,优先选择表锁。
- 如果业务写操作频繁,并发量高,且使用的是InnoDB引擎,优先选择行锁,同时要保证操作的WHERE条件命中索引,避免行锁升级为表锁。
- 如果需要批量操作整张表的数据,并且操作时间较长,可以考虑手动加表锁,避免操作过程中数据被其他会话修改。
注意:mysql的锁机制还有页锁、间隙锁等其他类型,本文仅对比最常用的表锁和行锁,实际使用中还需要结合具体的业务场景和存储引擎特性做选择。