0

我有一个表,其中插入的有效性是表中所有其他行的函数。该表管理资源的日期期间,并且仅当没有其他行与同一资源重叠时,新行才有效。

所以表是

resource_id INT
start_date DATE
end_date DATE

从概念上讲,如果以下查询的结果为 0,则可以插入一行(其中 newResourceId、newStartDate、newEndDate 是要插入的元组)

select count(entity_id) from mytable where resource_id = newResourceId and start_date < newEndDate and end_date > newStartDate;

问题是如何确保正确处理并发插入?我需要一些方法来确保在执行上述选择和随后的插入之间没有插入任何行。所以并发操作应该阻塞上面的选择,直到第一个操作提交。为此,我使用以下存储过程:

DELIMITER //
drop procedure if exists rp_insert //
create procedure rp_insert
(
    IN p_resource_id INT(10), 
    IN p_start_date DATE, 
    IN p_end_date DATE,
    OUT result INT
) 
BEGIN   
    DECLARE num_conflicts INT;
    DECLARE conflicts CURSOR FOR SELECT entity_id from mytable WHERE start_date < p_end_date and end_date > p_start_date and resource_id = p_resource_id FOR UPDATE; 

    START TRANSACTION;
    OPEN conflicts;
    SELECT FOUND_ROWS() into num_conflicts;
    if num_conflicts = 0 then
        insert ignore into mytable (resource_id, start_date, end_date) values (p_resource_id, p_start_date, p_end_date); 
        set result = LAST_INSERT_ID();      
        COMMIT;
    else
        set result = -2;
        ROLLBACK;
    end if; 
END //
DELIMITER ;

因此,如果有人能告诉我这是否是处理并发的正确方法,我将不胜感激,特别是:

  • 选择游标上的“更新”是否意味着具有重叠 where 条件的并发操作将在选择上阻塞,直到第一个事务提交/回滚?

  • 在相同的并发场景中,第二个操作 SELECT 是否会看到第一个操作插入的行(假设插入将在第二个事务开始后提交,但在第二个 select 执行之前)?

  • 智能数据库引擎是否会意识到我永远不会更新选择返回的任何行并决定不强制选择更新(例如,很容易看到事务中没有更新语句)

  • 关于性能的任何其他评论,是否有更好的方法等?

有关信息,我决定反对:

  • 将表锁定为矫枉过正(例如,如果选择结果中有重叠,则第二个操作只需要阻塞)。在 mysql 存储过程中似乎也不允许使用锁

  • 用 PHP 编码而不是存储过程(没有理由进行 db 往返,因为我不需要知道冲突是什么,只需知道插入是否成功)。

4

1 回答 1

0

好吧,冒着独白的风险,这就是我所做的:

以下解决方案似乎有效;我在对竞争条件感兴趣的 3 个点上测试了 sleep 语句的附加参数:

  • 在选择更新之前
  • 在选择更新之后但之前
  • 冲突测试之后但插入之前的冲突测试

然后我从两个不同的 mysql 会话中运行存储过程,其中一个会休眠,另一个会正常运行,有机会领先于第一个。在每种情况下,预期的存储过程都会插入一行,而另一个失败。所以这个实现对于并发调用是安全的

如果有人知道比这更好的方法或有其他意见,我仍然很感兴趣。我想只有当普通的数据库约束不能强制执行表的完整性时,才需要这样的事情。

DELIMITER //
create procedure rp_insert(
IN p_resource_id INT(10), IN p_start_date DATE, IN p_end_date DATE, OUT result INT)
BEGIN

    DECLARE num_conflicts INT DEFAULT 0;
    DECLARE num_locked_resources INT DEFAULT 0;
    DECLARE lock_resources CURSOR FOR SELECT resource_id from myresources WHERE resource_id = p_resource_id FOR UPDATE;
    DECLARE conflicts CURSOR FOR SELECT entity_id from mytable WHERE start_date < p_end_date and end_date > p_start_date and resource_id = p_resource_id;

    SET result = 0;
    START TRANSACTION;      
    OPEN lock_resources;
    SELECT FOUND_ROWS() into num_locked_resources;
    IF 0 < num_locked_resources THEN
        OPEN conflicts;
        SELECT FOUND_ROWS() into num_conflicts;
        if num_conflicts = 0 then
            insert ignore into mytable (resource_id, start_date, end_date) values (p_resource_id, p_start_date, p_end_date);
            SET result = LAST_INSERT_ID();
            COMMIT;
        else
            SET result = -2;
            ROLLBACK;
        end if; 
    ELSE
        set result = -3;
    END IF;
END //
DELIMITER ;
于 2013-06-25T21:00:47.467 回答