MySQL update 更新了比应有的更多的行

MySQL update updating more rows than it should be

我遇到更新语句应该(据我所知)更新 5 行的问题(我选择了 5 行到临时 table 并在更新语句中使用了 INNER JOIN)

但是,当涉及到 运行 更新语句时,它会更新任何可以选择到临时 table 中的内容,而不仅仅是临时 table 本身的合并内容。

我在选择语句中使用 FOR UPDATE 代码来锁定行,(因为我期望多个查询一次针对此 table,(注意删除这不会改变错误效果)

我已经概括了整个代码库,它仍然具有相同的效果,过去几天我一直在研究这个,我确信这只是我在做的一些愚蠢的事情

代码说明

TABLE `data`.`data_table Table 存储数据并显示它已被我的外部程序获取。

Stored Procedure `admin`.`func_fill_table 调试代码填充上面table.

Stored Procedure `data`.`func_get_data 实际代码旨在检索批量大小的记录,将它们标记为已拾取,然后 return 将它们发送到外部应用程序。

基本设置代码

DROP TABLE IF EXISTS `data`.`data_table`;
DROP PROCEDURE IF EXISTS `admin`.`func_fill_table`;
DROP PROCEDURE IF EXISTS `data`.`func_get_data`;
DROP SCHEMA IF EXISTS `data`;
DROP SCHEMA IF EXISTS `admin`;
CREATE SCHEMA `admin`;
CREATE SCHEMA `data`;

CREATE TABLE `data`.`data_table` (
  `identification_field_1` char(36) NOT NULL,
  `identification_field_2` char(36) NOT NULL,
  `identification_field_3` int(11) NOT NULL,
  `information_field_1` int(11) NOT NULL,
  `utc_action_time` datetime NOT NULL,
  `utc_actioned_time` datetime DEFAULT NULL,
  PRIMARY KEY (`identification_field_1`,`identification_field_2`,`identification_field_3`),
  KEY `NC_IDX_data_table_action_time` (`utc_action_time`)
);

程序创建

DELIMITER //

CREATE PROCEDURE `admin`.`func_fill_table`(
    IN records int
)
BEGIN
    IF records < 1
        THEN SET records = 50;
    END IF;

    SET @processed = 0;
    SET @action_time = NULL;

    WHILE @processed < records
    DO
        SET @action_time = DATE_ADD(now(), INTERVAL FLOOR(RAND()*(45)-10) MINUTE);#time shorter for temp testing
        SET @if_1 = UUID();
        SET @if_2 = UUID();
        INSERT INTO data.data_table(
            identification_field_1
            ,identification_field_2
            ,identification_field_3
            ,information_field_1
            ,utc_action_time
            ,utc_actioned_time)
        VALUES (
             @if_1
            ,@if_2
            ,FLOOR(RAND()*5000+1)
            ,FLOOR(RAND()*5000+1)
            ,@action_time
            ,NULL);

        SET @processed = @processed +1;
    END WHILE;
END
//
CREATE PROCEDURE `data`.`func_get_data`(
    IN batch int
)
BEGIN
    IF batch < 1
        THEN SET batch = 1; /*Minimum Batch Size of 1 */
    END IF;

    DROP TABLE IF EXISTS `data_set`;
    CREATE TEMPORARY TABLE `data_set` 
        SELECT 
            `identification_field_1` as `identification_field_1_local`
            ,`identification_field_2` as `identification_field_2_local`
            ,`identification_field_3` as `identification_field_3_local`
        FROM `data`.`data_table`
        LIMIT 0; /* Create a temp table using the same data format as the table but insert no data*/

    SET SESSION sql_select_limit = batch;

    INSERT INTO `data_set` (
        `identification_field_1_local`
        ,`identification_field_2_local` 
        ,`identification_field_3_local`)
    SELECT 
        `identification_field_1`
        ,`identification_field_2` 
        ,`identification_field_3` 
     FROM `data`.`data_table`
     WHERE
        `utc_actioned_time` IS NULL
            AND `utc_action_time` < NOW()
    FOR UPDATE; #Select out the rows to process (up to batch size (eg 5)) and lock those rows

    UPDATE 
       `data`.`data_table` `dt`
    INNER JOIN 
        `data_set` `ds`
        ON (`ds`.`identification_field_1_local` = `dt`.`identification_field_1`
            AND `ds`.`identification_field_2_local` = `dt`.`identification_field_2`
            AND `ds`.`identification_field_3_local` = `dt`. `identification_field_3`)
    SET `dt`.`utc_actioned_time` = NOW();
    # Update the table to say these rows are being processed

    select ROW_COUNT(),batch;
    #Debug output for rows altered (should be maxed by batch number)

    SELECT * FROM 
        `data`.`data_table` `dt`
    INNER JOIN 
        `data_set` `ds`
        ON (`ds`.`identification_field_1_local` = `dt`.`identification_field_1`
            AND `ds`.`identification_field_2_local` = `dt`.`identification_field_2`
            AND `ds`.`identification_field_3_local` = `dt`. `identification_field_3`);
    # Debug output of the rows that should have been modified

    SELECT 
       `identification_field_1_local`
        ,`identification_field_2_local` 
        ,`identification_field_3_local`
    FROM 
        `data_set`; /* Output data to external system*/

    /* Commit the in process field and allow other processes to access thoese rows again */
END;
//

运行代码

call `admin`.`func_fill_table`(5000);
call `data`.`func_get_data`(5);

您误用了 sql_select_limit 设置:

The maximum number of rows to return from SELECT statements.

它仅适用于 select 语句(限制结果 发送到客户端 ),不适用于 insert ... select ...。它旨在作为防止用户意外地被数百万结果淹没的保护措施,而不是作为另一个 limit 功能。

虽然通常不能为 limit 使用变量,但可以在存储过程中使用(对于 MySQL 5.5+):

The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants, with these exceptions: [...]

  • Within stored programs, LIMIT parameters can be specified using integer-valued routine parameters or local variables.

所以在你的情况下,你可以简单地使用

...     
FROM `data`.`data_table`
WHERE `utc_actioned_time` IS NULL AND `utc_action_time` < NOW()
LIMIT batch
FOR UPDATE;