4.4 事务与锁参数


4.4 事务与锁参数

本节摘要:事务和锁影响并发和数据一致。本节讲清楚隔离级别、锁参数、MVCC、死锁检测,平衡一致性与并发。

事务隔离级别

四种隔离级别(从低到高):

  • READ UNCOMMITTED:读未提交(脏读)。
  • READ COMMITTED(RC):读已提交(不可重复读)。
  • REPEATABLE READ(RR):可重复读(幻读)。
  • SERIALIZABLE:串行化(最安全,并发最低)。

MySQL InnoDB 默认 RR,PostgreSQL/Oracle 默认 RC。

权衡:

  • 级别越高一致性越强,并发越低(锁多)。
  • 级别越低并发越高,一致性越弱(脏读/不可重复读/幻读)。
  • 多数业务 RC 够用——避免脏读,允许不可重复读(业务可接受)。
  • 强一致要求用 RR/SERIALIZABLE——但并发降。

MVCC 多版本并发控制

MVCC:每行多版本,读不阻塞写,写不阻塞读。

原理:

  • 每行有版本(事务 ID)。
  • 读看符合隔离级别的版本——RC 看最新已提交,RR 看事务开始时版本。
  • 写创建新版本,旧版本保留给并发读。

好处:

  • 读不阻塞写,写不阻塞读——并发高。
  • 读不加锁——快。

代价:

  • 旧版本占空间(undo log)——需 VACUUM/purge 清理。
  • 长事务——旧版本不能清理,表膨胀。

MySQL InnoDB MVCC

  • undo log 存旧版本。
  • purge 线程清理无事务引用的旧版本。
  • 长事务阻塞 purge——表膨胀。

PostgreSQL MVCC

  • 旧版本存数据页(不单独 undo)。
  • VACUUM 清理死元组。
  • autovacuum 自动清理——要调好参数。

锁参数

innodb_lock_wait_timeout:行锁等待超时(秒,默认 50)。

  • 调短——快速失败重试,避免长时间等待。
  • 调长——给长事务时间完成。

innodb_deadlock_detect:死锁检测(8.0 默认 ON)。

  • ON:自动检测死锁回滚一个事务。
  • 高并发死锁多——检测开销大,可 OFF(但死锁需应用超时处理)。

lock_wait_timeout:元数据锁等待超时(MySQL)。

PG lock_timeout:锁等待超时(毫秒)。

autovacuum 调优(PostgreSQL)

PG MVCC 旧版本靠 VACUUM 清理。autovacuum 自动 VACUUM:

autovacuum:ON(默认),开启自动清理。

autovacuum_nap_time:轮询间隔(默认 1min),多久检查一次。

autovacuum_vacuum_threshold / autovacuum_analyze_threshold:触发阈值。

  • 默认 50——表有 50 行变化触发。
  • 大表调高(如 1000)避免频繁。

autovacuum_vacuum_scale_factor / autovacuum_analyze_scale_factor:触发比例。

  • 默认 0.2——20% 行变化触发。
  • 大表调低(如 0.05)——大表 20% 变化太多才清理,膨胀严重。

autovacuum_max_workers:并发 worker 数(默认 3)。

  • 调高(如 6-10)并行清理多表。
  • 但太多 worker 抢 IO。

问题

  • autovacuum 跟不上——表膨胀,查询慢。
  • 长事务阻塞 autovacuum——旧版本不能清理。
  • 解决:调参数、避免长事务、必要时手动 VACUUM。

MySQL purge 调优

innodb_purge_threads:purge 线程数(默认 4)。

  • 调高加速 undo 清理。

innodb_max_purge_lag:purge 滞后阈值。

  • 超过则延迟 DML——给 purge 时间。
  • 谨慎用——延迟 DML 影响性能。

问题

  • 长事务阻塞 purge——undo 堆积,表膨胀。
  • 解决:避免长事务、监控 History list length(应低)。

锁优化实践

1. 减少锁范围

  • 事务短——锁持有时间短。
  • WHERE 精确——锁少行而非全表。
  • 索引——行锁而非表锁(无索引升级表锁)。

2. 锁顺序一致

  • 多表更新按固定顺序——避免死锁。
  • 如总是先锁 A 再锁 B,不交叉。

3. 避免长事务

  • 长事务持锁久、阻塞 purge/VACUuum、占 undo。
  • 拆小事务——批量提交而非一个大事务。

4. 合适隔离级别

  • 多数 RC 够用——并发高。
  • 强一致 RR——但注意间隙锁(MySQL RR 有间隙锁,可能锁范围大)。

5. 监控锁

  • show engine innodb status——锁等待、死锁。
  • pg_locks(PG)——锁信息。
  • 锁等待多——优化查询/调隔离/拆事务。

⚠️ 常见误读:以为"隔离级别越高越好"。级别高一致性强但并发低(锁多)。多数业务 RC 够用,避免脏读,业务可接受不可重复读。强一致才用 RR/SERIALIZABLE。

💡 关键直觉:事务锁——隔离级别(READ UNCOMMITTED 脏读/RC 不可重复读多数够用/RR 幻读 MySQL 默认/SERIALIZABLE 最安全并发最低,级别高一致强并发低)。MVCC(多版本读不阻塞写,读不加锁快,旧版本占空间需清理,长事务阻塞清理表膨胀)。锁参数(lock_wait_timeout 调短快速失败、deadlock_detect ON 自动检测回滚高并发可 OFF)。autovacuum(PG MVCC 旧版本靠 VACUUM 清理,nap_time 轮询、threshold/scale_factor 触发阈值大表调低 0.05、max_workers 并发,跟不上表膨胀)。MySQL purge(purge_threads 调高、长事务阻塞 purge undo 堆积,监控 History list length)。锁优化(短事务减锁时间、WHERE 精确锁少行、索引行锁非表锁、锁顺序一致避死锁、避免长事务拆小、合适隔离 RC 多数/RR 强一致注意间隙锁、监控 innodb status/pg_locks)。

本节要点回顾

  • 隔离级别:READ UNCOMMITTED 脏读/READ COMMITTED 不可重复读(PG/Oracle 默认多数够用)/REPEATABLE READ 幻读(MySQL 默认,间隙锁锁范围大)/SERIALIZABLE 最安全并发最低。级别高一致强并发低,多数 RC 够用避免脏读业务可接受不可重复读,强一致用 RR/SERIALIZABLE。
  • MVCC:每行多版本读不阻塞写写不阻塞读,读不加锁快。RC 看最新已提交/RR 看事务开始版本。代价旧版本占空间需清理,长事务阻塞清理表膨胀。MySQL undo log+purge 线程,PG 旧版本存数据页+VACUUM。
  • 锁参数:innodb_lock_wait_timeout 行锁超时(默认 50 调短快速失败)、innodb_deadlock_detect 死锁检测(ON 自动回滚高并发可 OFF)、lock_wait_timeout 元数据锁、PG lock_timeout。
  • autovacuum(PG):ON 默认,nap_time 轮询间隔,threshold(默认 50 大表调高 1000)、scale_factor(默认 0.2 大表调低 0.05 避免 20% 才清理膨胀)、max_workers(默认 3 调 6-10 并行但抢 IO)。跟不上表膨胀,长事务阻塞,调参数/避免长事务/手动 VACUUM。
  • MySQL purge:purge_threads(默认 4 调高)、max_purge_lag(滞后延迟 DML 谨慎)、长事务阻塞 purge undo 堆积,监控 History list length 应低。
  • 锁优化:短事务减锁时间、WHERE 精确锁少行、索引行锁非表锁(无索引升级表锁)、锁顺序一致避死锁、避免长事务拆小批量提交、合适隔离(RC 多数/RR 强一致注意间隙锁)、监控 innodb status/pg_locks 锁等待多则优化。

隔离级别的工程选择矩阵

事务与锁的参数核心是隔离级别的选择,给一个工程决策矩阵。读已提交(多数商业库默认):杜绝脏读,允许不可重复读;适合绝大多数 OLTP 业务——订单、支付、库存的常规读写。可重复读(部分开源库默认):会话内一致性视图,配合间隙锁防幻影;代价是锁范围扩大、死锁概率上升,死锁频发的系统降级到读已提交常有意外的平静。串行化:正确性完美、并发近乎归零,只适合对账、审计这类"准准确确比快重要"的后台任务。快照隔离(部分库提供):读不加锁、写冲突检测,读多写少的报表场景的甜点。选择的元原则:隔离级别是"业务语义的需求",不是"越高越好的品质"——转账业务需要读已提交加显式行锁就足够,硬上串行化是把性能白白祭天;反过来,对一致性有真实要求的场景(超卖防护)就要在应用层加乐观锁或用足够强的隔离,含糊的中间态是最危险的。把这张矩阵贴在团队 Wiki,每次新业务的技术评审过一遍"这个业务的隔离需求是什么",锁参数的调优就前置成了设计决策。

死锁的治理三步法

锁参数章收官谈死锁的系统性治理。第一步,检测与记录:死锁检测开到最灵敏,每次死锁的完整现场(双方事务的语句、锁的粒度、持有与等待关系)自动留存——死锁日志是锁设计缺陷的免费诊断报告。第二步,模式归类:把一段时间的死锁记录按模式聚类,通常会发现八成的死锁集中在两三个模式(同两表反序更新、唯一键并发插入、间隙锁与范围更新的冲突)——治理对象是模式不是个案。第三步,对症改造:反序更新改统一访问序(所有事务按主键升序更新)、并发插入改预取号段、间隙锁冲突评估隔离级别降级或改写条件——每类模式都有成熟解药。三步法跑通后,死锁率应该进入个位数的月度量级;此时若还有零星死锁,多为业务逻辑的天然竞争(秒杀类),交给应用层的乐观锁去解。死锁治理是锁章节知识的综合应用场:检测参数(发现)、隔离级别(成因分析)、锁粒度(改造)全部上阵——把它当作本章的毕业考试题来准备。

锁等待的现场解剖流程

锁参数章给一个现场流程——发生严重锁等待时按此解剖。第一步,抓现场:立刻导出当前锁等待关系(谁持锁、谁等待、等了多久、各自在执行什么语句)——现场几分钟后可能自行解除,证据不可再生。第二步,定性:区分"长事务持锁"(某事务开了一小时没提交——应用连接泄漏或忘提交)与"热点竞争"(大量短事务挤同一行——秒杀或序列号)——两者药方完全不同。第三步,止血:长事务杀掉(评估业务影响后),热点竞争临时扩行(把单行计数改成多行取模轮询)或限流。第四步,根治:长事务修应用(连接池加泄漏检测、事务边界收紧),热点改设计(预分配、异步化、队列化)。第五步,归档:现场数据、定性结论、根治方案入库,成为下次秒判的先例。五步流程的血泪经验在第一步——多数团队的问题不是不会处理,是处理完才发现没留证据,同样的坑第二年再踩一遍。

锁参数的默认值审读

锁章收官建议一次"默认值审读"——把库的锁相关默认参数列出来,对照业务读一遍,这是多数团队没做过但一小时能完成的事。审读清单:锁等待超时(默认常见几十秒——对在线业务太长,用户早刷新了页面,事务还挂着;收到五秒内)、死锁检测间隔(默认通常够用,高并发微调收紧)、隔离级别默认值(不同库默认不同,你的业务知道自己在哪个级别吗——多数团队答不上来,这就是审读的价值)、最大锁数量与锁内存(超高并发系统的隐形天花板,知道在哪即可)。审读的产出是一页"锁参数与业务对照表"——每行写清默认值、我们的值、为什么。做完这页纸,锁层就从"出了事再查文档"变成"心里有账"——参数治理的颗粒度就到这了,再往下是第 2 章的语句与索引功夫。

收尾提醒:锁参数的一切调整都要以"锁等待监控"为伴——没有锁等待的指标面板,锁参数就是盲调;上线监控在先、调整参数在后,这个顺序不是流程洁癖,是因果律。面板上的等待时长分布与死锁计数,就是锁层健康的心电图,学会读它,锁章的功课才算完整。

补一个与监控联动的收尾:锁参数治理完成后,把"锁等待总量趋势"加入周报——不是为了告警(告警已有),是为了让趋势可见:锁等待的缓慢上升(每月百分之几)是业务并发自然增长与热点形成的先导指标,提前看到它,扩行改造与热点治理就能在事故前立项。锁层的终局管理不是消灭锁(不可能),而是让锁的增长曲线始终在视野里、在计划里——这又是全书"预测优于救火"总纲的一次具体落地。

再补一个跨文化的观察:不同业务团队对锁等待的耐受度天差地别(交易团队秒级告警、报表团队分钟级无感),所以锁参数从不宜全局一刀切——分级配置(按应用或按资源队列区分超时与优先级)才是成熟形态;这也再次说明参数调优的尽头是业务理解,懂业务脾气的参数才调得准。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U