导读:本期聚焦于小伙伴创作的《SQL如何利用锁进行层级树状数据更新?自底向上加锁防死锁策略详解》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《SQL如何利用锁进行层级树状数据更新?自底向上加锁防死锁策略详解》有用,将其分享出去将是对创作者最好的鼓励。

层级树状数据广泛存在于各类业务系统中,比如商品分类、部门架构、评论回复等场景,这类数据通常通过父节点ID关联形成树形结构。当需要对树状数据进行批量更新时,比如调整某个节点的层级位置、修改整棵子树的属性,多事务并发操作很容易因为加锁顺序不一致引发死锁。自底向上的加锁策略是解决这类问题的有效方案,通过固定从叶子节点到根节点的加锁顺序,避免循环等待的死锁条件。

SQL如何利用锁进行层级树状数据更新?自底向上加锁防死锁策略详解

层级树状数据的常见存储结构

最常见的树状数据存储方式是邻接表模型,通过parent_id字段记录节点的父节点,结构示例如下:

-- 树状节点表结构
CREATE TABLE tree_node (
    id INT PRIMARY KEY AUTO_INCREMENT,
    node_name VARCHAR(50) NOT NULL,
    parent_id INT DEFAULT NULL,
    node_level INT NOT NULL,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    -- 外键关联父节点,可选
    FOREIGN KEY (parent_id) REFERENCES tree_node(id)
);

假设我们有如下树形结构:根节点A(id=1),子节点B(id=2)、C(id=3),B的子节点D(id=4)、E(id=5),这就是典型的邻接表存储的层级树。

死锁产生的核心原因

当多个事务同时更新同一棵树的节点时,如果加锁顺序不一致,就会产生死锁。比如事务1先更新节点B(id=2),再尝试更新节点A(id=1);事务2先更新节点A(id=1),再尝试更新节点B(id=2)。此时事务1持有B的锁等待A的锁,事务2持有A的锁等待B的锁,就会形成循环等待,触发死锁。

死锁的四个必要条件是互斥、占有且等待、不可剥夺、循环等待,自底向上的加锁策略就是破坏循环等待条件,让所有事务都按照相同的顺序加锁。

自底向上加锁的实现逻辑

自底向上加锁的核心思路是:更新某个节点时,先锁定该节点的所有子节点,再逐层向上锁定父节点,直到根节点。这样所有事务的加锁顺序都是从叶子节点到根节点,不会出现循环等待。

步骤1:获取目标节点的所有子节点ID

首先需要递归查询出目标节点的所有后代节点ID,包括自身。不同数据库递归查询语法略有差异,MySQL 8.0+可以使用CTE实现:

-- 查询id=2的节点(B节点)的所有后代节点ID,包含自身
WITH RECURSIVE sub_tree AS (
    SELECT id, parent_id FROM tree_node WHERE id = 2
    UNION ALL
    SELECT t.id, t.parent_id FROM tree_node t
    INNER JOIN sub_tree st ON t.parent_id = st.id
)
SELECT id FROM sub_tree;

上述查询会返回节点2、4、5,也就是B节点及其所有子节点。

步骤2:按ID升序加锁

获取到所有需要更新的节点ID后,按照ID从小到大排序,依次对这些行加排他锁。因为ID通常是自增的,子节点的ID一般大于父节点,按ID升序加锁就相当于自底向上加锁:

-- 开启事务
START TRANSACTION;

-- 查询并锁定所有后代节点,按ID升序加锁
WITH RECURSIVE sub_tree AS (
    SELECT id, parent_id FROM tree_node WHERE id = 2
    UNION ALL
    SELECT t.id, t.parent_id FROM tree_node t
    INNER JOIN sub_tree st ON t.parent_id = st.id
)
SELECT id, node_name, node_level FROM sub_tree ORDER BY id ASC FOR UPDATE;

-- 执行更新操作,比如更新所有子树的层级
UPDATE tree_node SET node_level = node_level + 1 
WHERE id IN (
    WITH RECURSIVE sub_tree AS (
        SELECT id FROM tree_node WHERE id = 2
        UNION ALL
        SELECT t.id FROM tree_node t
        INNER JOIN sub_tree st ON t.parent_id = st.id
    )
    SELECT id FROM sub_tree
);

-- 提交事务
COMMIT;

步骤3:处理跨树的更新场景

如果需要同时更新多个不相关的树节点,需要先对所有目标节点的根节点ID排序,再逐个处理每个节点的子树,避免不同子树之间的加锁顺序冲突。

策略的注意事项

  • 递归查询的性能:如果树层级很深、节点数量很多,递归查询可能会有性能问题,可以结合冗余的层级路径字段优化查询,比如增加path字段存储从根节点到当前节点的路径,如/1/2/4。
  • 锁的持有时间:尽量缩短事务长度,在加锁后尽快完成更新操作并提交,减少锁的持有时间,提升并发性能。
  • 隔离级别选择:建议使用可重复读或者串行化隔离级别,避免幻读导致漏加锁的问题,不同数据库的默认隔离级别不同,需要根据实际情况调整。
  • 避免长事务:如果事务中包含大量节点的更新,会持有大量行锁,容易阻塞其他事务,建议批量操作时分批次处理。

不同数据库的适配说明

上述示例基于MySQL 8.0+,如果是其他数据库,递归查询语法需要调整:

数据库类型递归查询语法
PostgreSQL使用WITH RECURSIVE,语法和MySQL类似
SQL Server使用WITH RECURSIVE或者CTE递归查询
Oracle使用CONNECT BY PRIOR语法,比如SELECT id FROM tree_node START WITH id=2 CONNECT BY PRIOR id = parent_id

加锁的核心逻辑是一致的,都是先获取所有后代节点,再按固定顺序加锁,不同数据库只是查询语法有差异。

策略效果验证

我们可以通过模拟两个并发事务来验证死锁是否被避免:

-- 事务1:更新节点2的子树
START TRANSACTION;
WITH RECURSIVE sub_tree AS (
    SELECT id FROM tree_node WHERE id = 2
    UNION ALL
    SELECT t.id FROM tree_node t INNER JOIN sub_tree st ON t.parent_id = st.id
)
SELECT id FROM sub_tree ORDER BY id ASC FOR UPDATE;
-- 此时事务1持有2、4、5的锁

-- 事务2:更新节点1的子树(包含节点2)
START TRANSACTION;
WITH RECURSIVE sub_tree AS (
    SELECT id FROM tree_node WHERE id = 1
    UNION ALL
    SELECT t.id FROM tree_node t INNER JOIN sub_tree st ON t.parent_id = st.id
)
SELECT id FROM sub_tree ORDER BY id ASC FOR UPDATE;
-- 事务2会尝试先锁1,再锁2,但2已经被事务1锁定,事务2会等待事务1释放锁,不会死锁

可以看到,两个事务都按照ID升序加锁,不会出现循环等待,有效避免了死锁问题。

SQL锁机制层级树状数据自底向上加锁死锁预防修改时间:2026-07-17 04:24:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。