Любопытное поведение MySQL при обновлении записей с помощью временной таблицы - PullRequest
1 голос
/ 22 марта 2020

представьте, у вас есть две таблицы.

Данные заполняются внешним процессом в одну из таблиц (здесь называемых " event ") и обрабатываются вызовом хранимой процедуры, которая принимает некоторые параметры и пытается найти записи в таблице "событие". Если он находит его, он создает запись во второй таблице « action » и помечает совпадающие записи в «event», чтобы предотвратить их повторную обработку в случае повторного запуска процедуры.

При нормальных обстоятельствах я использовал бы «курсор для обновления» в таблице «событие» и установил поле для обработки в l oop курсора. Но, похоже, MySQL не поддерживает это. Поэтому я искал альтернативный путь. Я просто храню идентификаторы совпадающих записей таблицы «событие» во временной таблице, а позже пытаюсь обновить таблицу «событие» с ограничением на идентификаторы, хранящиеся во временной таблице.

Процедура должна быть вызывается сценарием оболочки, который устанавливает переменную на сегодняшнюю дату и передает ее в процедуру. Я ожидаю, что обновляются только записи таблицы «событие» с идентификаторами из временной таблицы. Но происходит следующее: обновляются все записи таблицы «событие» с полем «событие», представленное сегодня.

Кто-нибудь может попытаться объяснить это? Я не могу найти свою ошибку ...

Вы можете сами проверить поведение.

Это создает таблицы (измените имя базы данных, чтобы соответствовать вашему):

use yourdatabasename;
drop table if exists event;
create table if not exists event
(
    id integer not null AUTO_INCREMENT,
    sender varchar(127) not null,
    name varchar(127) not null,
    submitted Timestamp DEFAULT CURRENT_TIMESTAMP,
    status varchar(127) not null,
    logged Timestamp DEFAULT CURRENT_TIMESTAMP,
    ub_status varchar(127) not null,
    ub_status_date Timestamp DEFAULT CURRENT_TIMESTAMP,
    primary key (id)
);

-- create some entries in "event" with decending timestamps
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name1', now(), 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name2', now() - interval 1 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name3', now() - interval 2 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name4', now() - interval 3 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name5', now() - interval 4 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name6', now() - interval 5 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name7', now() - interval 6 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name8', now() - interval 7 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;
insert into event (sender, name, submitted, status, logged,ub_status) values ('192.168.0.2', 'name9', now() - interval 8 hour, 'COMPLETED_SUCCESS',now(),'CREATED') ;


drop table if exists action;
create table if not exists action
(
    id integer not null AUTO_INCREMENT,
    name varchar(127) not null,
    sender varchar(127) not null,
    logged Timestamp not null DEFAULT CURRENT_TIMESTAMP,
    status varchar(127) not null,
    status_date Timestamp DEFAULT CURRENT_TIMESTAMP,
    primary key (id)
);

Это является хранимой процедурой:

CREATE DEFINER=`flo_db`@`localhost` PROCEDURE `TestUpdateActionTwoJobs`(IN process_name varchar(40), 
                                                                IN sendingMachine varchar(127),
                                                                IN jobname1 varchar(127), 
                                                                IN jobname2 varchar(127), 
                                                                IN startdate varchar(10), 
                                                                IN update_event_table int)
begin
    DECLARE found_count long;
    DECLARE exit handler for sqlexception
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE, 
            @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;
        SET @full_error = CONCAT("ERROR ", @errno, " (", @sqlstate, "): ", @text);
        SELECT @full_error;
        ROLLBACK;
    END;
    DECLARE exit handler for sqlwarning
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE, 
            @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;
        SET @full_error = CONCAT("ERROR ", @errno, " (", @sqlstate, "): ", @text);
        SELECT @full_error;
        ROLLBACK;
    END;

   /* The procedure searches for certain records in table "event" - some fields have to match, some come
    * from stored procedure parameters.
    * If there is a match a new record is to be created in table "action" and the found records 
    * in table "event" are to marked as "processed" by setting the column "event.ub_status" to "PROCESSED"
    */
   SET @found_count = 0;
   SET @found_tmp = 0;
   START TRANSACTION;
       select count(*) from event  where
       sender = sendingMachine 
       and status = 'COMPLETED_SUCCESS'
       and submitted > startdate
       and (name = jobname1 or name = jobname2)
       and ub_status = 'CREATED' into @found_count ;
       /* we expect exactly 2 */
       IF @found_count = 2 THEN 
         CREATE TEMPORARY TABLE IF NOT EXISTS tmp_UpdateActionTwoJobs
            select id from event  where
                sender = sendingMachine 
                and status = 'COMPLETED_SUCCESS'
                and submitted > startdate
                and (name = jobname1 or name = jobname2)
                and ub_status = 'CREATED';

         INSERT INTO action (name, sender, logged, status, status_date) VALUES (process_name, sendingMachine, 
                       NOW(), 'CREATED', NOW());
         /* count the number of records in the temporary table */
         select count(*) from tmp_UpdateActionTwoJobs into @found_tmp;
         SET @info = CONCAT("number of records in tmp-table: ", @found_tmp);
         /* mark the records as processed if wanted */
         IF update_event_table = 1 THEN
            UPDATE event SET ub_status = 'PROCESSED', ub_status_date = NOW() WHERE id in (select id tmp_UpdateActionTwoJobs );
         END IF;
       else
         set @info = "no condition met!";
       END IF;  
   COMMIT;
   /* generate info output */
   SELECT @info;
end

Чтобы вызвать процедуру, вы можете использовать такой скрипт bash (пользователь, пароль, хост и имя базы данных должны быть заменены ...):

#!/bin/bash
# we are looking for entries in "event" that were created today
today=$(date '+%Y-%m-%d')
mysql -u database_user -p'password' -h host -e "call TestUpdateActionTwoJobs('TargetJobname', '192.168.0.2', 'name2', 'name3', '$today', 1);" database_name

Большое спасибо!

1 Ответ

0 голосов
/ 23 марта 2020

Я переписал вам процедуру для работы с курсором, так что нет необходимости во временной таблице. Ваш запрос работает, но мои настройки безопасности не позволяют этого, потому что

ОШИБКА 1175 (HY000): вы используете безопасный режим обновления и пытались обновить таблицу без WHERE, в котором используется столбец KEY ,

Но я использовал

select id FROM tmp_UpdateActionTwoJobs ;

вместо того, что вы написали.

Так что попробуйте мою хранимую процедуру

USE `testdb`;
DROP procedure IF EXISTS `TestUpdateActionTwoJobs`;

DELIMITER $$
USE `testdb`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `TestUpdateActionTwoJobs`(IN process_name varchar(40), 
                                                                IN sendingMachine varchar(127),
                                                                IN jobname1 varchar(127), 
                                                                IN jobname2 varchar(127), 
                                                                IN startdate varchar(20), 
                                                                IN update_event_table int)
begin
    DECLARE found_count long;
    DECLARE _id long;
    DECLARE finished INTEGER DEFAULT 0;
    DEClARE curid 
        CURSOR FOR 
            select id from event  where
                sender = sendingMachine 
                and status = 'COMPLETED_SUCCESS'
                and submitted > startdate
                and (name = jobname1 or name = jobname2)
                and ub_status = 'CREATED';

    -- declare NOT FOUND handler
    DECLARE CONTINUE HANDLER 
        FOR NOT FOUND SET finished = 1;    
    DECLARE exit handler for sqlexception
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE, 
            @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;
        SET @full_error = CONCAT("ERROR ", @errno, " (", @sqlstate, "): ", @text);
        SELECT @full_error;
        ROLLBACK;
    END;
    DECLARE exit handler for sqlwarning
    BEGIN
        GET DIAGNOSTICS CONDITION 1 @sqlstate = RETURNED_SQLSTATE, 
            @errno = MYSQL_ERRNO, @text = MESSAGE_TEXT;
        SET @full_error = CONCAT("ERROR ", @errno, " (", @sqlstate, "): ", @text);
        SELECT @full_error;
        ROLLBACK;
    END;

   /* The procedure searches for certain records in table "event" - some fields have to match, some come
    * from stored procedure parameters.
    * If there is a match a new record is to be created in table "action" and the found records 
    * in table "event" are to marked as "processed" by setting the column "event.ub_status" to "PROCESSED"
    */
   SET @found_count = 0;
   SET @found_tmp = 0;
   START TRANSACTION;

       select count(*) from event  where
       sender = sendingMachine 
       and status = 'COMPLETED_SUCCESS'
       and submitted > startdate
       and (name = jobname1 or name = jobname2)
       and ub_status = 'CREATED' into @found_count ;

        SET @found_tmp = 0;
        IF @found_count = 2 THEN
            INSERT INTO action (name, sender, logged, status, status_date) VALUES (process_name, sendingMachine, 
               NOW(), 'CREATED', NOW());
            OPEN curid;
                getid: LOOP
                    FETCH curid INTO _id;
                    IF finished = 1 THEN 
                        LEAVE getid;
                    END IF;
                    UPDATE event SET ub_status = 'PROCESSED', ub_status_date = NOW() WHERE id = _id;
                    SET @found_tmp = @found_tmp +1;
                END LOOP getid;
            CLOSE curid;
            SET @info = CONCAT("number of records in processed: ", @found_tmp);
       ELSE
         set @info = "no condition met!";            
            /* we expect exactly 2 */
        END IF;

   COMMIT;
   /* generate info output */
   SELECT @info;
end$$

DELIMITER ;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...