Asher& 2024-11-21 20:33 采纳率: 66.7%
浏览 19

mysql duplicate entry问题。一张表加了联合唯一索引,然后更新批量数据时报duplicate entry错误。

问题: 一张表加了联合唯一索引,然后更新批量数据时报duplicate entry错误。
为了实现前端将文件夹拖动排序功能,并在数据库中更新顺序属性(seq)。

img

# 文件夹表
CREATE TABLE `folder`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `parent_id` int(11) NULL DEFAULT NULL COMMENT '父级id',
  `category` int(11) NULL DEFAULT NULL COMMENT '分类',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称',
  `seq` int(11) NULL DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`) USING BTREE
)

联合唯一索引是这三个字段:category_parentid_seq; 意思是在某个分类中,同级的顺序都是唯一(同级就是parent_id一样)。

然后我自己写了个过程函数:如下:
```yaml
#  这里p_id=当前文件夹的id, p_category=分类,p_new_seq=要插入的新位置; 
#  p_parent_id=父id(如果是同级移动,则不变,如果是放到别的文件夹里头,或者从别的文件夹拖拽到外层时这个p_parent_id需要更新)
CREATE DEFINER=`root`@`localhost` PROCEDURE `update_sequence`(IN `p_id` bigint,IN `p_category` bigint,IN `p_new_seq` int,IN `p_parent_id` int)
BEGIN
       DECLARE current_seq INT;

    -- 获取当前记录的 seq 值
    SELECT seq INTO current_seq
    FROM folder
    WHERE id = p_id;

    -- 为当前记录设置一个临时 seq 值,避免冲突
    UPDATE folder
    SET seq = -1
    WHERE id = p_id;

    -- 如果新的 seq 小于当前 seq,则将范围内的 seq 加 1
    IF p_new_seq < current_seq THEN
        UPDATE folder
        SET seq = seq + 1
        WHERE category = p_category
          AND parent_id = p_parent_id
          AND seq >= p_new_seq
          AND seq < current_seq;

    -- 如果新的 seq 大于当前 seq,则将范围内的 seq 减 1
    ELSEIF p_new_seq > current_seq THEN
        UPDATE folder
        SET seq = seq - 1
        WHERE category = p_category
      AND parent_id = p_parent_id
          AND seq > current_seq
          AND seq <= p_new_seq;
    END IF;

    -- 最后将当前记录的 seq 更新为新的值
    UPDATE im_library_folder
    SET seq = p_new_seq, parent_id = p_parent_id
    WHERE id = p_id;

END

问题出现在递增seq或递减seq时报duplicate entry错误。估计是它依次递增或递减时与原本的数据冲突了,并不是同时执行的。

例如:

img

如果将id为5的移动到1的位置时,过程函数的顺数如下:
1. 将当前id=5的seq赋值为-1,为了防止冲突;
2.由于新的位置1 < 原来的的位置3,所以1~2之间的seq递增;
  这时 id=1的递增后的数据是 category=1,parent_id=0,seq=2,就会跟id=6的数据冲突。这是问题的关键。
我猜问题是出现在这里,所以不知道如何修改。
  • 写回答

1条回答 默认 最新

  • 关注

    以下回复参考:皆我百晓生券券喵儿等免费微信小程序作答:

    您的问题主要在于在更新 seq 字段时,您是先标记为 -1 避免冲突,然后再执行更新操作,但这种处理方式存在竞态条件。在您的例子中,当您尝试将 id=5 的记录移动到 seq=1 的位置时,由于您首先将其 seq 更新为 -1,然后递增或递减其他记录的 seq 值,这可能导致其他记录的 seq 值在您更新 id=5 的记录之前已经发生了变化,从而导致冲突。

    为了解决这个问题,您可以考虑以下步骤:

    1. 使用事务确保操作的原子性:确保整个更新过程在一个事务中完成,这样可以确保更新操作的完整性和一致性。如果在更新过程中发生错误,可以回滚事务,避免数据不一致。
    2. 避免直接修改 seq:您可以考虑使用一种方法,在不直接修改 seq 值的情况下更新排序。例如,可以维护一个额外的字段来跟踪顺序变更的历史或用于确定哪些记录需要重新排序。这种方法可以更精确地控制排序过程并避免冲突。
    3. 重新设计更新逻辑:在您的情况下,可能需要在更新 seq 值之前先确定哪些记录的 seq 值可能会受到影响,并预先调整它们。这样可以避免在更新过程中发生冲突。

    一个可能的解决方案是:

    1. 首先确定需要移动的记录的当前 seq 值和它在列表中的位置。
    2. 确定哪些记录的 seq 值会因为移动而改变,并预先调整它们的 seq 值。
    3. 执行移动操作并更新 seq 值。
    4. 确保所有这些操作都在一个事务中完成。

    在实施这些更改时,建议在一个测试环境中进行测试以确保数据的一致性和准确性。此外,为了确保数据库操作的效率和性能,可能需要考虑数据库的性能调优和索引优化等措施。

    评论

报告相同问题?

问题事件

  • 创建了问题 11月21日