并发丢数据深度剖析:JED 的锁机制与事务实战踩坑及解决方案
1、理论来源于实践
现象:于 2025-08-13 21:45:35,事实逻辑表将自身的指标与维度同步到原子服务的实现时,出现同步过来的指标与维度丢失。
核心原因:两次重复的事实逻辑表同步时间非常相近,导致同步过来的指标与维度丢失。
2、倒带进事故现场
逻辑表向原子服务同步的核心逻辑是 “先删后增”:删除旧数据→对比新老数据→插入新增数据,具体流程如下:
整体业务代码精简逻辑如下:
用一个请求进行举例:
共计 15 个指标,64 个维度
核心结论点:
1.请求 2 的删除操作被阻塞了,直到请求 1 执行完整个方法。
2.请求 2 中去查看当前实现的指标的时候,发现库里已经存在所有指标不会进行新增,与上一步删除的逻辑相悖。
3、结论点深度剖析
3.1 分析结论一
请求 2 的删除操作被阻塞了,直到请求 1 执行完整个方法。
3.1.1 复习 JED(京东自研数据库)的锁机制
3.1.1.1 不是“一把锁”,而是 “锁矩阵”
3.1.1.2 一张图总结:锁的 “决策逻辑”
3.1.2 理论应用实践
3.1.2.1 本次事故的物料:
JED 的表结构:
3.1.2.2 实践分析
通过 JED 的锁决策,可以得出
JED 的索引:KEY `idx_metric_def_id` (`metric_def_id`,`logic_table_id`)
删除写操作,不符合最左匹配原则,因此为表 x 锁。
因此请求 2 的删除操作需要等待请求 1 的事务释放表锁后才可继续进行,符合当时场景。
3.2 分析结论二
请求 2 中去查看当前实现的指标的时候,发现库里已经存在所有指标不会进行新增,与上一步删除的逻辑相悖。
3.2.1 复习 JED 的事务
3.2.1.1 ACID 不是 "四个独立特性",而是 "因果链"
•一句话:ACID 的核心是一致性,其他三个特性都是为了实现它的手段。
•一致性(Consistency):一致性确保事务将数据库从一个一致的状态转变到另一个一致的状态。即使在多个事务同时执行的情况下,数据库也能保持数据的一致性。
•原子性(Atomicity):事务是 "不可分割的工作单元"(要么全成,要么全败),是一致性的前提(如果步骤能拆分,中间失败就会破坏一致性)。
•隔离性(Isolation):通过控制多事务并发规则,避免互相干扰,是一致性的保障(并发混乱会直接破坏一致性)。
•耐久性(Durability):事务提交后结果永久保存,是一致性的最终落点(否则重启后数据丢失,之前的一致性白搭)。
3.2.1.2 隔离级别:不是 "越严越好",而是 "成本与需求的平衡术"
JED 的 4 种隔离级别,本质是用 "数据可见性" 换 "并发性能"的选择:
3.2.1.3 MVCC:事务的 "平行宇宙" 机制(为什么读写不冲突?)
多版本并发控制是 "无锁读" 的核心,它让读和写像在平行宇宙中运行:
底层逻辑(用 "时间戳" 理解):
•每个事务启动时,会拿到一个全局递增的事务 ID(trx_id)。
•每行数据隐藏 3 个字段:
◦DB_TRX_ID:最后修改该行的事务 ID;
◦DB_ROLL_PTR:指向 undo 日志的指针(存储历史版本);
◦DB_DELETED:标记是否删除(逻辑删除)。
读操作的 "幻术":
•快照读(普通 SELECT):只看 "事务 ID ≤ 自己 ID" 且 "未被删除" 的版本,完全不加锁。 例:事务 A(ID=100)查询时,会忽略所有被 ID>100 的事务修改的数据。
包含 4 个核心字段:
•m_ids:生成 Read View 时,当前活跃的事务 ID 列表(未提交的事务)。
•min_trx_id:m_ids中最小的事务 ID。
•max_trx_id:下一个将要分配的事务 ID(非活跃事务 ID,仅用于判断 “未来事务”)。
•creator_trx_id:生成该 Read View 的事务自身 ID。
可见性判断规则(一条记录是否对当前事务可见,取决于其 “最后修改事务 ID”,记为db_trx_id):
1.若 db_trx_id == creator_trx_id:可见(自己修改的自己可见)。
2.若 db_trx_id < min_trx_id:可见(修改记录的事务在当前快照生成前已提交)。
3.若 db_trx_id >= max_trx_id:不可见(修改记录的事务在当前快照生成后才启动)。
4.若 min_trx_id ≤ db_trx_id < max_trx_id:
◦若 db_trx_id 在 m_ids 中:不可见(该事务仍活跃,未提交)。
◦若 db_trx_id 不在 m_ids 中:可见(该事务已提交)。
5.当前读(加锁读 / 写操作):读取最新版本,并加锁防止其他事务修改。
3.2.1.4 事务日志:"安全与性能" 平衡术
事务能既保证 durability 又不慢,全靠两大日志:
1.redo log(重做日志):
◦作用:崩溃后恢复未写入磁盘的数据(保证 durability)。
◦反直觉:事务提交时,数据先写 redo log(内存 + 磁盘),再异步刷到数据文件(这叫 WAL 技术)。
◦为什么快?redo log 是顺序写(磁盘顺序写比随机写快 100 倍 +)。
2.undo log(回滚日志):
◦作用:保存数据修改前的版本,用于事务回滚(保证 atomicity)和 MVCC 快照读。
◦注意:undo log 会被 purge 线程定期清理(当没有事务需要旧版本时)。
3.2.1.5 终极心法:事务设计的 "3 个凡是"
1.凡是不需要事务的操作,坚决不用(如日志插入可关闭自动提交,批量提交)。
2.凡是能在 RC 解决的,绝不升 RR(互联网业务优先选 RC,用业务逻辑防不可重复读)。
3.凡是大事务,必拆分成 "读 - 算 - 写" 三步(读阶段不加锁,算阶段在应用层,写阶段用最短事务加锁)。
记住:事务的本质不是 "约束",而是 "工具"—— 能解决问题的最简单事务,才是最好的事务。
3.2.2 理论应用实践
3.2.2.1 本次事故的物料:
表的事务等级:
需要删除的指标实现(根据实现 id):
需要插入的指标实现:
3.2.2.2 实践分析:
用 sql 模拟两个事务的执行过程:
事务 1:
事务 2:
流程图(用一行数据进行演示版本控制):
为何事务 1 的 select 查询出“为空”,事务 2 的 select 查询出“不为空”:
4.解决办法
为了解决事务 2 的查询"不为空"的问题,分别列出以下方案:
当前落地情况:已通过 “分布式锁控制同一逻辑表同步并发” 的短期方案解决事故,后续将在业务迭代中推进 “读 - 算 - 写” 拆分的长期优化,进一步降低事务粒度与锁冲突风险。
5.附录
5.1 名词解释
事实逻辑表:由物理数仓中的事实表和维度逻辑表关联形成的语义表,可以描述业务过程的详细信息,是指标的数据来源。
原子服务:指标的实现方式,一个指标可以有多个实现。







评论