Php 针对非简单情况处理MySQL存储过程中的并发性
我有一个表,其中insert的有效性是表中所有其他行的函数。该表管理资源的日期周期,只有当没有其他行与同一资源重叠时,新行才有效 所以这张桌子是Php 针对非简单情况处理MySQL存储过程中的并发性,php,mysql,stored-procedures,concurrency,Php,Mysql,Stored Procedures,Concurrency,我有一个表,其中insert的有效性是表中所有其他行的函数。该表管理资源的日期周期,只有当没有其他行与同一资源重叠时,新行才有效 所以这张桌子是 resource_id INT start_date DATE end_date DATE 从概念上讲,如果以下查询的结果为0,则可以插入一行(其中newResourceId、newStartDate、newEndDate是要插入的元组) 从mytable中选择count(entity_id),其中resource_id=newResourceId,
resource_id INT
start_date DATE
end_date DATE
从概念上讲,如果以下查询的结果为0,则可以插入一行(其中newResourceId、newStartDate、newEndDate是要插入的元组)
从mytable中选择count(entity_id),其中resource_id=newResourceId,start_datenewStartDate;
问题是如何确保正确处理并发插入?我需要某种方法来确保在执行上述选择和后续插入之间不会插入任何行。因此,在提交第一个操作之前,并发操作应阻止上述选择。为此,我使用以下存储过程:
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 ;
分隔符//
放置程序(如果存在)rp\U插入//
创建程序rp\U插入
(
在p_资源_id INT(10)中,
在p_开始日期中,
在p_结束日期中,
输出结果整数
)
开始
声明num_冲突INT;
声明从mytable中选择实体\u id的冲突游标,其中开始\u日期p\u开始\u日期和资源\u id=p\u资源\u id进行更新;
启动交易;
公开冲突;
选择find_ROWS()进入num_冲突;
如果num_conflicts=0,则
将ignore插入mytable(资源id、开始日期、结束日期)值(资源id、开始日期、结束日期);
设置结果=最后一次插入ID();
犯罪
其他的
设置结果=-2;
回降;
如果结束;
结束//
定界符;
因此,如果有人能告诉我这是否是处理并发性的正确途径,我将不胜感激,特别是:
- select游标上的“For update”是否意味着具有重叠where条件的并发操作将在select上阻塞,直到第一个事务提交/回滚
- 在相同的并发场景中,第二个操作是否会选择查看第一个操作插入的行(假定插入将在第二个事务开始后提交,但在第二个选择执行之前提交)
- 智能数据库引擎是否有可能意识到我从未更新select返回的任何行,并决定不强制执行select for update(例如,很容易看到事务中没有update语句)
- 对性能有任何其他评论,是否有更好的方法等
- 将表锁定为溢流(例如,如果选择结果中存在重叠,则第二次操作只需阻塞)。mysql存储过程中似乎也不允许使用锁
- 用PHP而不是存储过程编码(没有理由做db往返,因为我不需要知道冲突是什么,只需要知道插入是否成功)
- 在选择更新之前
- 在“选择更新”之后但在“选择更新”之前
- 冲突测试在冲突测试之后但在插入之前
- 好吧,冒着独白的风险,我做了以下几件事:
下面的解决方案似乎有效;我在3个对比赛条件感兴趣的点上测试了睡眠语句的附加参数:
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 ;
分隔符//
创建程序rp\U插入(
在p_资源_id INT(10)、p_开始_日期、p_结束_日期、输出结果INT)中)
开始
声明num_冲突INT默认值为0;
声明num_locked_resources INT默认值为0;
声明lock_resources游标,用于从myresources中选择resource_id,其中resource_id=p_resource_id进行更新;
声明从mytable中选择实体\u id的冲突游标,其中开始\u日期p\u开始\u日期和资源\u id=p\u资源\u id;
设置结果=0;
启动交易;
打开锁定资源;
选择find_ROWS()进入num_locked_资源;
如果0
还有一件事-是否有办法确保仅允许通过此存储过程对此表进行插入?我在mysql权限中找不到任何内容。。理想情况下,我还可以定义某些列是不可变的,不能通过更新来更改。实际上,我认为这种方法不起作用。。如果在第一个操作中选择了0行,则第二个操作可能会发生冲突,但由于没有“为更新选择”行,因此不存在块。修改后的方法可以是在资源id上“选择更新”,可能是在一个单独的表上,该表包含每个资源id的一行。。除非有人知道更好的方法?
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 ;