Порядок оценки присваивания переменной в SELECT может отличаться от порядка возвращаемых строк. При каких условиях это может произойти? - PullRequest
1 голос
/ 08 июля 2019

Недавно я пытался использовать пользовательскую переменную для сбора некоторой информации из строки last , возвращенной в моем наборе результатов.

Я имею в виду, например, если у меня есть список имен от «Аарон» до «Заркс»,

SELECT @n:=Name FROM people ORDER BY Name;
SELECT @n;

Второй SELECT должен вернуть 'Zzarx'.

Это простой случай. Это работает как ожидалось; назначение переменных надежно происходит в том же порядке, в котором строки отправляются клиенту, поэтому последнее назначение соответствует последней возвращенной строке.

Но странные вещи, кажется, происходят, когда запрос более сложен:

SELECT DISTINCT IFNULL(@n:=Name,'unknown') FROM people ORDER BY <some non-indexed expression> LIMIT 10;
SELECT @n;

Выполнение чего-то подобного в MariaDB v10.3.16 Я получаю конечное значение @n (из второго SELECT) , которое не соответствует ни одной из строк, возвращаемых первым SELECT! . (Обратите внимание, что Name - это столбец NOT NULL, поэтому IFNULL() на самом деле является избыточным, но все еще необходим для запуска этого поведения).

Обратите внимание, что это происходит только тогда, когда ВСЕ из следующего:

  • SELECT DISTINCT
  • ORDER BY не может использовать индекс
  • Переменная присваивается внутри некоторого выражения

Моя теория такова:

  • SELECT DISTINCT вызывает раннюю оценку возвращенных выражений столбцов.
  • ORDER BY (non-indexed expression) вызывает явную операцию сортировки после оценки данных столбца.
  • Механизм SQL достаточно умен, чтобы распознавать простой шаблон SELECT @var := (expression) и оценивать @var только при отправке строки клиенту, но не может выполнить эту оптимизацию, если назначение @var:=... встроено в большее выражение , как в IFNULL() в моем примере.

Однако это всего лишь догадки. Страница руководства по пользовательским переменным на самом деле не говорит ничего полезного (ни MySQL, ни MariaDB).

Мне кажется, что использование @variable для захвата чего-либо из последней возвращаемой строки в многострочном запросе является полезным и, вероятно, довольно распространенным трюком, но сейчас я не уверен, можно ли полагаться на него или когда Это. Точно так же для множества нумерованных строк и других хитроумных схем, которые я видел, которые используют @variables в части набора результатов SELECT.

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

... Потому что это очень важная вещь для понимания!


Другой, немного менее патологический пример:

Скажем, таблица t имеет 1000 строк:

SET @n:=0;
SELECT @n:=@n+1 FROM t ORDER BY 1 DESC LIMIT 5;
SELECT @n;

Возвращенные наборы результатов:

1000
 999
 998
 997
 996

и

1000

Обратите внимание, что еще раз, окончательное значение @n НЕ соответствует последней возвращенной строке, и действительно, учитывая семантику запроса, в этом случае это не может.

1 Ответ

2 голосов
/ 08 июля 2019

Хотя вы не используете 8.0.13, скоро будет следующее. Вы нашли причину, почему это происходит.

----- 2018-10-22 8.0.13 Общая доступность - - Важное изменение -----

Установка пользовательских переменных в операторах, отличных от SET is теперь устарел из-за проблем, которые включали перечисленные здесь:

Порядок вычисления для выражений с участием пользовательских переменных был не определено.

Тип результата по умолчанию для переменной основан на ее типе в начало утверждения, которое может иметь непредвиденные последствия, когда переменная, содержащая значение одного типа в начале оператора было присвоено новое значение другого типа в том же операторе.

предложения HAVING, GROUP BY и ORDER BY при обращении к переменной которому было присвоено значение в списке выражений выбора, не сработало как и ожидалось, потому что выражение было оценено на клиенте и так было возможно, что устаревшие значения столбцов из предыдущей строки б.

Синтаксис, такой как SELECT @var, @var: = @ var + 1, все еще принят в MySQL 8.0 для обратной совместимости, но подлежит удалению в будущем выпуске.

- Из «журнала изменений».

Думайте о DISTINCT как о GROUP BY.

SELECT @v := ... FROM t ORDER BY x;

Случай 1: INDEX(x), но Оптимизатор может выбрать выборку строк, а затем отсортировать их.

Случай 2: INDEX(x) и оптимизатор выбирает выборку строк на основе индекса.

SELECT @v := ... FROM t  GROUP BY w  ORDER BY x;

Для этого почти наверняка требуется создать временную таблицу (для упорядочивания), может быть две (одну для группировки и одну для упорядочения). Единственный рациональный способ выполнить запрос - это оценить выражения (включая @v) в SELECT, собрать результаты, затем перейти к группированию и упорядочению. Таким образом, порядок оценки, скорее всего, не будет x. Но это может подражать w.

А как насчет PARTITIONing? В настоящее время нет никакого параллелизма в оценке MySQL SELECT. Но что, если это появилось? Давайте возьмем «очевидный» случай - отдельные потоки, работающие над отдельными PARTITIONs таблицы. Все ставки выключены в порядке оценки.

Как только это будет реализовано, как насчет разделения даже 10-секционного SELECT для получения некоторого параллелизма?

Вы не собираетесь выиграть спор.

Да, он может оставаться "устаревшим" в течение длительного времени. Или, может быть, будет sql_mode, который выполняет запросы «старым» способом. Или существование @variables препятствует определенной оптимизации (в пользу предсказуемости). И т.д.

Могу ли я предложить вам написать «запрос функции» на bugs.mysql.com с указанием того, что вы хотели бы видеть. (Вы также можете сделать это на mariadb.com, но они смотрят на первое.)

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