(此问题出现场景为 mysql5.7.31 隔离级别为可重复读)
在做审核业务流程中自定义了两张表,a 和 a_detail 。主要需求是子表所有记录都审核通过了,那么就去修改主表记录为审核通过。 但有可能会遇到最后两条记录同时审核成功,他们去查询当前子表审核记录的时候有可能对方还没提交,所以最终会修改主表失败。为了解决这个问题,就直接想到了添加索引后,利用 mysql 行锁加在主表的记录上,获取锁之后再去 count 。但是在操作过程中 在获取行锁之前添加了一句查询子表的 sql 导致结果不正确。请教下这是为什么?或者说请教下此业务有其他合适的解决方案吗?
CREATE TABLE `a` (
`uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '审核记录主表 guid',
`approval_status` int(11) NULL DEFAULT 1 COMMENT '审核状态(1:审核中,2:通过)',
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 id',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_guid`(`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '审核记录主表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of a
-- ----------------------------
INSERT INTO `a` VALUES ('a1', 1, 1);
INSERT INTO `a` VALUES ('a2', 1, 2);
CREATE TABLE `a_detail` (
`approval_status` int(11) NULL DEFAULT 1 COMMENT '审核状态(1:审核中;2:通过)',
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 id',
`auid` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_aid`(`auid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '审核记录明细表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of a_detail
-- ----------------------------
INSERT INTO `a_detail` VALUES (1, 1, 'a1');
INSERT INTO `a_detail` VALUES (1, 2, 'a1');
INSERT INTO `a_detail` VALUES (1, 3, 'a2');
INSERT INTO `a_detail` VALUES (1, 4, 'a2');
会话一和 会话二 同时执行
SELECT @@GLOBAL.TX_ISOLATION;
begin;
-- 查询字表明细记录(这部如果去除整个流程正常)-
SELECT * FROM a_detail WHERE id = 1
-- 根据索引利用行锁 锁定主表的记录--
SELECT * FROM `a` where uid='a1' for update;
-- 跟新字表状态为 2 审核通过--
UPDATE a_detail set approval_status = 2 WHERE id = 1 and approval_status = 1;
-- 查看子表是不是都已经审核通过了--
select count(0) from a_detail WHERE auid = 'a1' and approval_status!=2;
-- 接下来如果 count(0) 数量是 0 代表所有子记录都通过,则去修改主表状态 update a set approval_status=2 where uid='a1' --
commit;
SELECT @@GLOBAL.TX_ISOLATION;
begin;
-- 查询字表明细记录(这部如果去除整个流程正常)-
SELECT * FROM a_detail WHERE id = 2
-- 根据索引利用行锁 锁定主表的记录--
SELECT * FROM `a` where uid='a1' for update;
-- 跟新字表状态为 2 审核通过--
UPDATE a_detail set approval_status = 2 WHERE id = 2 and approval_status = 1;
-- 查看子表是不是都已经审核通过了--
select count(0) from a_detail WHERE auid = 'a1' and approval_status!=2;
-- 接下来如果 count(0) 数量是 0 代表所有子记录都通过,则去修改主表状态 update a set approval_status=2 where uid='a1' --
commit;
在做审核业务流程中自定义了两张表,a 和 a_detail 。主要需求是子表所有记录都审核通过了,那么就去修改主表记录为审核通过。 但有可能会遇到最后两条记录同时审核成功,他们去查询当前子表审核记录的时候有可能对方还没提交,所以最终会修改主表失败。为了解决这个问题,就直接想到了添加索引后,利用 mysql 行锁加在主表的记录上,获取锁之后再去 count 。但是在操作过程中 在获取行锁之前添加了一句查询子表的 sql 导致结果不正确。请教下这是为什么?或者说请教下此业务有其他合适的解决方案吗?
CREATE TABLE `a` (
`uid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '审核记录主表 guid',
`approval_status` int(11) NULL DEFAULT 1 COMMENT '审核状态(1:审核中,2:通过)',
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 id',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_guid`(`uid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '审核记录主表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of a
-- ----------------------------
INSERT INTO `a` VALUES ('a1', 1, 1);
INSERT INTO `a` VALUES ('a2', 1, 2);
CREATE TABLE `a_detail` (
`approval_status` int(11) NULL DEFAULT 1 COMMENT '审核状态(1:审核中;2:通过)',
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键 id',
`auid` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_aid`(`auid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 74 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '审核记录明细表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of a_detail
-- ----------------------------
INSERT INTO `a_detail` VALUES (1, 1, 'a1');
INSERT INTO `a_detail` VALUES (1, 2, 'a1');
INSERT INTO `a_detail` VALUES (1, 3, 'a2');
INSERT INTO `a_detail` VALUES (1, 4, 'a2');
会话一和 会话二 同时执行
SELECT @@GLOBAL.TX_ISOLATION;
begin;
-- 查询字表明细记录(这部如果去除整个流程正常)-
SELECT * FROM a_detail WHERE id = 1
-- 根据索引利用行锁 锁定主表的记录--
SELECT * FROM `a` where uid='a1' for update;
-- 跟新字表状态为 2 审核通过--
UPDATE a_detail set approval_status = 2 WHERE id = 1 and approval_status = 1;
-- 查看子表是不是都已经审核通过了--
select count(0) from a_detail WHERE auid = 'a1' and approval_status!=2;
-- 接下来如果 count(0) 数量是 0 代表所有子记录都通过,则去修改主表状态 update a set approval_status=2 where uid='a1' --
commit;
SELECT @@GLOBAL.TX_ISOLATION;
begin;
-- 查询字表明细记录(这部如果去除整个流程正常)-
SELECT * FROM a_detail WHERE id = 2
-- 根据索引利用行锁 锁定主表的记录--
SELECT * FROM `a` where uid='a1' for update;
-- 跟新字表状态为 2 审核通过--
UPDATE a_detail set approval_status = 2 WHERE id = 2 and approval_status = 1;
-- 查看子表是不是都已经审核通过了--
select count(0) from a_detail WHERE auid = 'a1' and approval_status!=2;
-- 接下来如果 count(0) 数量是 0 代表所有子记录都通过,则去修改主表状态 update a set approval_status=2 where uid='a1' --
commit;
