У меня есть процедура, в которой я увеличиваю идентификатор вручную, итеративно добавляя случайное число к значению идентификатора. Иногда это приводит к тому, что идентификатор не увеличивается, хотя я могу подтвердить, что оператор (FLOOR(RAND()*(1000-2+1))+2) всегда возвращает значение от 2 до 1000. И все же следующее простое утверждение:

SET `nextid` = `nextid` + (FLOOR(RAND()*(1000-2+1))+2);

По-прежнему не удается увеличить время от времени nextid. Если я выделю свое значение (FLOOR(RAND()*(1000-2+1))+2) в отдельную переменную и добавлю две переменные, этого не произойдет. Ниже приведен минимальный пример тестового примера, который воспроизводит поведение.

DELIMITER $$
CREATE PROCEDURE `test`()
BEGIN
    DECLARE `nextid` BIGINT DEFAULT 793991813529600000;
    DECLARE `previous` BIGINT DEFAULT 0;
    DECLARE `i` BIGINT DEFAULT 0;
    DECLARE `random` BIGINT DEFAULT 0;

    WHILE `i` < 100000 DO
        SET `previous` = `nextid`;
        -- Produce a random number between 2 and 1000.
        SET `random` = (FLOOR(RAND()*(1000-2+1))+2);
        SET `nextid` = `nextid` + `random`;

        -- Error if the nextid is no different from the previous ID.
        -- This is successful every time. 
        IF `nextid` = `previous` THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Oh no!', MYSQL_ERRNO = 2000;
        END IF;

        SET `i` = `i` + 1;
    END WHILE;

    SET `i` = 0;
    WHILE `i` < 100000 DO
        SET `previous` = `nextid`;
        -- Increment the ID by a random number and do the set in a single statment.
        SET `nextid` = `nextid` + (FLOOR(RAND()*(1000-2+1))+2);

        -- This fails randomly, but reliably.
        IF `nextid` = `previous` THEN
            SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Whoops!', MYSQL_ERRNO = 2000;
        END IF;

        SET `i` = `i` + 1;
    END WHILE;

END $$
DELIMITER ;

CALL `test`();
DROP PROCEDURE IF EXISTS `test`;

Похоже, это может быть какая-то проблема с кастингом, но я не могу найти никаких подтверждений. Итак, мой вопрос: что вызывает такое поведение?

Дополнительные детали;

mysql --version
mysql  Ver 14.14 Distrib 5.7.24, for Linux (x86_64) using  EditLine wrapper
1
Rudi Kershaw 6 Ноя 2018 в 16:48

1 ответ

Лучший ответ

Вот что, скорее всего, произойдет:

  • RAND() возвращает число с плавающей запятой
  • FLOOR(FLOAT VALUE) возвращает число с плавающей запятой
  • BIGINT VALUE + FLOAT VALUE возвращает число с плавающей запятой
  • Поплавки неточные, и именно здесь у вас есть проблема

Одно из значений nextid, которое мне не удалось, - 793991813579709184. Посмотрите, что произойдет, если вы добавите к нему 50f ... значение с плавающей запятой не увеличивается:

CREATE TABLE t (i BIGINT);
INSERT INTO t VALUES (793991813579709184);
INSERT INTO t VALUES (793991813579709184 + 50e0);
SELECT * FROM t;
| 793991813579709184 |
| 793991813579709184 |

В вашем тестовом коде я изменил эту строку, и она сработала так, как ожидалось:

SET `nextid` = `nextid` + CAST(RAND() * 999 AS SIGNED) + 2;
1
Salman A 6 Ноя 2018 в 14:25