本文目录导读:
并发冲突(通常指数据库或共享资源场景下的竞争条件)的优化核心思路是降低锁的粒度、减少持有锁的时间以及利用无锁或乐观机制,针对不同场景,可以从以下几个方面来优化并减少冲突概率:
优化锁的粒度与范围
这是最直接有效的方法,锁定的数据越少,其他事务能访问的未锁定部分就越多,冲突概率自然降低。
- 行锁代替表锁:这是最基本的,确保数据库使用行级锁(如 InnoDB),而不是表级锁(如 MyISAM)。
- 减少锁的覆盖范围:在写操作时,只锁定绝对必要的数据行,在一个订单表中,如果只需要更新某一行的状态字段,不要不小心锁定了关联的用户行或整个分区。
- 索引优化与锁数量:行锁是加在索引记录上的,如果查询没有走索引,数据库可能升级为锁住更多的行(甚至全表)。确保 WHERE 条件中的字段有合适的索引。
减少持有锁的时间
锁持有时间越短,其他事务等待的时间窗口越小,冲突概率越低。
- 将耗时操作移出事务:不要在事务中执行外部 API 调用、写日志文件、发送邮件、复杂的图像处理或网络等待。
- 反例:
BEGIN; UPDATE inventory SET stock = stock - 1 WHERE id = 1; -- 这里有1秒钟的网络延迟或调用第三方支付 -- 其他事务在这1秒内无法更新该行,极易冲突 COMMIT;
- 正例:先准备好所有数据,最后在事务中仅执行数据库操作。
- 反例:
- 快速提交:事务一旦完成业务逻辑,立即提交,而不是等待其它逻辑。
- 使用短事务:避免开启一个大事务,修改几十万行数据,拆分成多个小批量事务,每个小事务快速提交。
选择合适的并发控制策略
根据业务对数据一致性的要求,选择不同的策略。
- 乐观锁 (Optimistic Locking):非常适合读多写少、冲突概率低的场景。
- 原理:不锁数据,更新时携带一个版本号(
version)或时间戳,在UPDATE ... WHERE id = ? AND version = ?时,检查版本是否被修改,如果影响行数为0,说明有人先修改了,需要重试。 - 优势:几乎无锁开销,性能高。
- 劣势:冲突发生时需要重试(应用程序逻辑来处理)。
- 原理:不锁数据,更新时携带一个版本号(
- 悲观锁 (Pessimistic Locking):适合写操作多、冲突概率高的场景(如抢库存)。
- 原理:
SELECT ... FOR UPDATE,直接锁定行,保证临界区安全,但也制造了阻塞。 - 应用:必须谨慎,防止死锁。
- 原理:
- 应用层排队:将并发变为串行化。
- 场景:抢红包、秒杀扣减库存。
- 做法:将同一资源(如商品ID)的请求通过Redis 分布式锁或内存队列(如Channel) 串行化处理,对所有针对同一个
product_id的请求,只允许一个 goroutine/线程去执行数据库写入。
数据库架构与隔离级别优化
- 使用更低的隔离级别:默认隔离级别较高会增加锁等待。
READ COMMITTED(读已提交)通常比REPEATABLE READ(可重复读)或SERIALIZABLE(可序列化)的锁更少,并发性更高,在不需要幻读保护的场景下,降低隔离级别。
- 避免热点行 (Hot Row):在设计之初,尽量避免一个账户/一个商品/一个计数器被上亿请求同时更新。
- 策略:分片,将原本一个账户的余额
balance拆分成balance_1,balance_2……balance_10,每次更新时随机选择一个子账户进行更新(对于加分操作),查询时汇总所有子账户,这能显著降低单行冲突概率。
- 策略:分片,将原本一个账户的余额
应用层代码优化
- 重试机制:在高并发系统中,冲突是常态,设计应用代码时,对于因冲突导致的失败(如乐观锁更新失败、死锁回滚),实现指数退避的重试逻辑,但要注意限制最大重试次数,避免系统雪崩。
- 引入中间层缓存:对频繁读取但很少变化的数据(如商品描述、用户昵称),使用 Redis 等本地或分布式缓存,减少对数据库的直接读写,从而降低并发冲突。
总结与选择建议
| 优化方向 | 具体技术 | 最佳适用场景 |
|---|---|---|
| 减少锁范围 | 行锁、索引优化 | 所有数据库操作 |
| 缩短锁时间 | 小事务、事务外耗时操作 | 任何涉及事务的业务 |
| 乐观并发控制 | 版本号机制 | 读多写少,冲突概率低的场景 (如用户资料修改) |
| 悲观并发控制 | SELECT FOR UPDATE |
写密集型,冲突非常激烈的场景 (如秒杀库存扣减) |
| 应用层串行化 | 消息队列、Redis 锁 | 强一致性要求极高,且冲突概率极大的热点资源 (如银行转账、防重复) |
| 架构解耦 | 缓存、分片 | 突破单点、热点瓶颈 |
最后建议:不要一开始就上复杂的分布式锁或队列,先从索引优化 + 行锁 + 小事务入手,往往能解决 80% 的问题,如果冲突依然严重,再根据业务场景选择乐观锁(常见财务系统)或应用层排队(常见秒杀系统)。
标签: 重试机制