在高并发业务场景中,多个线程同时对同一数据库表执行读写操作时,很容易出现读写冲突问题,比如读取到未提交的中间数据、写入操作被长时间阻塞、事务死锁等,这些问题会直接影响系统的稳定性和响应效率。解决这类问题的核心思路是合理优化数据库的锁机制,平衡数据一致性和并发性能。

高并发读写冲突的常见场景
读写冲突通常出现在以下场景中:
- 多个事务同时修改同一行数据,后发起的事务需要等待前一个事务释放锁才能执行
- 事务隔离级别设置过高,导致读操作也会加排他锁,阻塞写操作
- 没有合理设计索引,查询时触发全表扫描,加锁范围扩大到整个表
- 长事务持有锁的时间过长,导致后续大量请求排队等待
SQL锁机制的基础类型
共享锁与排他锁
共享锁(S锁)用于读操作,多个事务可以同时持有同一资源的共享锁,互不阻塞;排他锁(X锁)用于写操作,持有排他锁时,其他事务无法再获取该资源的任何锁,会被阻塞。
以下是MySQL中手动加共享锁和排他锁的示例代码:
-- 开启事务 BEGIN; -- 对id=1的行加共享锁,其他事务可以读但不能写这行数据 SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 提交事务,释放锁 COMMIT; BEGIN; -- 对id=1的行加排他锁,其他事务无法读写这行数据 SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 提交事务,释放锁 COMMIT;
意向锁
意向锁是表级锁,用于标识事务后续要对表中的行加哪种类型的锁,分为意向共享锁(IS)和意向排他锁(IX)。它的作用是提高锁冲突检测的效率,避免判断行锁冲突时需要遍历表中所有行。
锁机制优化实用技巧
调整事务隔离级别
不同的事务隔离级别对应不同的锁策略,开发者可以根据业务对数据一致性的要求选择合适的级别:
| 隔离级别 | 锁策略 | 适用场景 |
|---|---|---|
| 读未提交 | 几乎不加锁,读取时不会阻塞其他事务 | 对数据一致性要求极低的临时统计场景 |
| 读已提交 | 写操作加排他锁,读操作不加锁,使用快照读 | 大部分普通业务场景,平衡一致性和性能 |
| 可重复读 | 读操作加共享锁,范围查询会加间隙锁 | 对数据一致性要求较高的金融类场景 |
| 串行化 | 所有读写操作都加表级锁,完全串行执行 | 数据一致性要求极高的特殊场景 |
优化索引减少锁范围
如果查询语句没有命中索引,数据库会执行全表扫描,此时加锁范围会扩大到整个表,大幅提升冲突概率。因此需要为频繁作为查询条件的字段建立合适的索引,让锁只加在符合条件的少量行上。
建立索引的示例代码:
-- 为user表的phone字段建立普通索引,减少查询时的锁范围 CREATE INDEX idx_user_phone ON user(phone);
缩短锁持有时间
事务持有锁的时间越长,冲突概率越高。优化方式包括:
- 将事务中不必要的查询操作移到事务外部,减少事务内的操作逻辑
- 避免在事务中执行耗时操作,比如调用外部接口、处理大文件等
- 尽量让事务中的SQL语句按顺序执行,避免交叉执行导致死锁
合理选择锁粒度
锁粒度越小,并发性能越好。行级锁的并发性能优于表级锁,因此在支持行级锁的数据库(如MySQL的InnoDB引擎)中,尽量使用行级锁,避免手动使用LOCK TABLES语句加表级锁。
错误使用表级锁的示例:
-- 不建议手动加表级锁,会阻塞整个表的读写操作 LOCK TABLES user WRITE; UPDATE user SET age = 20 WHERE id = 1; UNLOCK TABLES;
避免死锁
死锁是两个或多个事务互相持有对方需要的锁,导致所有事务都无法继续执行的情况。避免死锁的技巧包括:
- 所有事务按照相同的顺序访问表和行
- 尽量一次性申请所有需要的锁,减少锁申请的次数
- 设置合理的事务超时时间,超过时间自动回滚释放锁
优化效果验证
优化完成后,可以通过数据库的慢查询日志、锁等待监控指标来验证效果。如果慢查询数量减少、锁等待时间下降,说明优化方案起到了作用。如果业务允许,还可以进行压测,对比优化前后的接口响应时间和吞吐量,确认锁机制优化的实际收益。