Как MySQL определяет, является ли вставка уникальной?

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

Если я правильно понимаю, то я предполагаю, что следующее будет правда:

Пример 1: У вас есть таблица с 1 млрд строк. Каждая строка имеет уникальный столбец UUID. Если вы выполняете вставку, сервер должен сделать что-то вроде подразумевается SELECT COUNT(*) FROM table WHERE UUID = [new uuid] и определить, если количество равно 0 или 1. Правильно?

Пример 2: У вас есть таблица с 1 млрд строк. Каждая строка имеет составной уникальный ключ, состоящий из даты и UUID. Если вы выполняете вставку, сервер должен сделать что-то вроде подразумевается SELECT COUNT(*) FROM table WHERE DATE = [date] AND UUID = [new uuid] и проверьте, равен ли счетчик 0 или 1. Да?

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

предположим Ваши 1 миллиард строк равномерно и последовательно распределены по 2000 различным датам. Не означает ли это, что случай 2 выполнит вставку быстрее, потому что он может искать UUID, сегментированные на даты? Если нет, то было бы лучше использовать случай 1 для скорости вставки - и в этом случае почему?

этот вопрос является теоретическим, поэтому не беспокойтесь о рассмотрении регулярной производительности выбора в этом случае. Первичным ключом не будет индекс даты UUID+.

как ответ к комментариям: UUID в моем случае разработан исключительно с целью избежать дублирования записей из-за плохих соединений. Поскольку вы не можете сделать одну и ту же запись для другой даты дважды (без того, чтобы она логически была новой записью), UUID не должен быть глобально уникальным - он должен быть уникальным только для каждой даты. Вот почему я могу позволить ему быть частью составного ключа.

3 ответов


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

ссылаясь только на InnoDB...

индекс (включая уникальный и первичный ключ) является BTree. BTrees очень эффективны для размещения одной строки на основе ключа, на котором сортируется BTree. (Он также эффективен при сканировании в ключевом порядке.) "Веер" типичного BTree в MySQL составляет порядка 100. Итак, для миллиона строк BTree составляет около 3 уровни глубокие (log100 (миллион)); для триллиона строк он только в два раза глубже (приблизительно). Таким образом, даже если ничего не кэшируется, требуется всего 3 попадания на диск, чтобы найти одну конкретную строку в миллионном индексе.

Я здесь свободен с " индексом "против" таблицы", потому что они по существу одинаковы (по крайней мере, в InnoDB). Оба-БТРы. Чем отличается то, что находится в листовых узлах: листовые узлы стол BTree имеет все столбцы. (Я игнорирую off-block хранение текста / BLOB в InnoDB.) Индекс (кроме первичного ключа)имеет копию первичного ключа в листовом узле. Вот как вторичный ключ может попасть из индекса BTree в остальные столбцы строки, и как InnoDB не должен хранить несколько копий все столбцы.

первичный ключ "кластеризован" с данными. Это один BTree содержит все столбцы всех строк, и он упорядочен в соответствии с первичным ключом спецификация.

поиск записи по первичному ключу один BTree поиск. Поиск записи по вторичному ключу два BTree ищет, один в BTree вторичного индекса, который дает вам первичный ключ; затем второй для детализации данных / PK BTree.

ПЕРВИЧНЫЙ КЛЮЧ (UUID)... Так как UUID очень random," следующая "строка, которую вы вставляете, будет расположена в "случайном" месте. Если таблица намного больше, чем кэшируется в buffer_pool, блок, в который должна войти новая строка, скорее всего, не будет кэшироваться. Это приводит к попаданию диска, чтобы вытащить блок в кэш (пул буферов), и в конечном итоге другой диск ударил, чтобы записать его обратно на диск.

поскольку первичный ключ является уникальным ключом, одновременно происходит что-то еще (без выбора COUNT(*) и т. д.). Уникальность проверяется после извлечения блока и до принятия решения о том, давать ли ошибку "дубликат ключа" или хранить строку. Кроме того, если блок является "полным", тогда блок должен быть "разделен", чтобы освободить место для новой строки.

индекс(UUID) или уникальный(UUID)... Для этого индекса есть BTree. На INSERT, некоторые случайно расположенный блок должен быть извлечен, изменен, возможно, разделен и записан обратно на диск, очень похоже на обсуждение PK выше. Если у вас есть уникальный(UUID), также будет проверка уникальности и, возможно, сообщение об ошибке. В любом случае, сейчас и/или позже есть диск Я/О.

AUTO_INCREMENT PK... Если первичный ключ является auto_increment, то новые записи добавляются в "последний" блок в BTree данных. Когда он заполняется (каждые 100 или около того записей), есть (логически) разделение блока и сброс старого блока на диск. (На самом деле, ввод-вывод, вероятно, задерживается и выполняется в фоновом режиме.)

первичный ключ (id) + уникальный(UUID)... Два Дерева. На вставке есть активность в обоих. Это, вероятно, будет хуже чем просто ПЕРВИЧНЫЙ КЛЮЧ (UUID). Добавьте хиты диска выше, чтобы увидеть, что я имею в виду.

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

теперь для вашего секретного соуса... Первичный ключ (дата, UUID)... Вы позволяете одному и тому же UUID появляться в два разных дня. Это может помочь! Вернемся к тому, как работает ПК и проверка уникальности... "Составной" индекс (дата, UUID) проверяется на уникальность при вставке записи. Записи сортируются по дате+UUID, поэтому все сегодняшние записи сгруппированы вместе. Если (и это может быть большое "если") данные за один день помещаются в пул буферов (но не вся таблица), то это происходит каждое утро... Вставки внезапно добавляют новые записи в" конец "таблицы из-за новой"даты". Эти вставки происходят случайным образом в пределах новой даты. Блоки в buffer_pool выталкиваются на диск, чтобы освободить место для новых блоков. Но, приятно, что вы видите гладкие, быстрые вставки. Это не похоже на то, что вы видели с первичным ключом(UUID), когда многим строкам приходилось ждать чтения диска, прежде чем уникальность могла быть проверена. Все сегодняшние блоки остаются кэшированными, и вам не нужно ждать ввода-вывода

но если вы когда-нибудь станете настолько большими, что не сможете поместить данные за один день в пул буферов, все начнет замедляться, сначала в конце дня, а затем будет ползти все раньше и раньше по мере увеличения частоты вставок.

кстати, раздел по диапазону (дата) вместе с первичным ключом (uuid, дата) имеет несколько схожие характеристики. (Да, я намеренно перевернул столбцы PK.)


при вставке больших объемов данных в таблицу имейте в виду, что данные в конечном итоге физически хранятся на диске где-то. Чтобы фактически читать и записывать данные с диска, MySQL (и большинство других СУБД) использует что-то под названием кластерный индекс. Если в таблице указан первичный ключ или уникальный индекс, столбец или столбцы, участвующие в ключе/индексе, становятся ключом кластеризованного индекса. Это означает, что на диске, данные физически хранятся в в том же порядке, что и значения в ключевых столбцах.

используя кластеризованный индекс, компонент database engine может быстро определить, существует ли значение, не сканируя всю таблицу. Теоретически, если таблица содержит N = 1.000.000 записей, движку в среднем требуется log2 (N) = 20 операций, чтобы проверить, существует ли значение, независимо от того, сколько столбцов участвует в индексе. Для вторичных индексов обычно используется B-дерево или хэш-таблица (поиск в интернете для них термины, для подробного объяснения того, как они работают).

заключение в этой статье неправильно:

"... MySQL не может буферизировать достаточно данных, чтобы гарантировать, что значение уникально и поэтому причинено выполнить большущее количество значение для каждой вставки, чтобы гарантировать уникальность"

Это неправильно. Проверка уникальности на самом деле не требует дополнительной работы, так как двигатель должен был найти место для вставки новой записи в любом случае. Что вызывает замедление производительности, это использование UUID. Помните, что UUID генерируются случайным образом при вставке новой записи. Это означает, что новая запись должна быть вставлена в произвольном физическом положении на диске, и это приводит к перемещению существующих данных, чтобы разместить новую запись. Если, с другой стороны, столбец индекса является значением, которое монотонно увеличивается (например, auto-increment INT), новые записи всегда будут вставляется после последней записи, что означает, что никакие существующие данные никогда не нужно будет перемещать.

в вашем случае, не будет никакой разницы в производительности между случаем 1 и случаем 2. Но вы все равно столкнетесь с проблемами из-за случайности UUID. Было бы намного лучше, если бы вы использовали значение автоматического увеличения вместо UUID. Кроме того, поскольку UUID всегда уникальны по своей природе, на самом деле нет смысла индексировать их с уникальным ограничением. Кроме того, если вы действительно необходимо использовать UUID, убедитесь, что у вас есть первичный ключ на вашей таблице, который основан на автоматическом приращении INT, чтобы гарантировать, что новые записи никогда случайно не вставляются на диск.


это сама цель UNIQUE ограничения:

A UNIQUE index создает ограничение, так что все значения в индексе должны быть разными. Ошибка возникает при попытке добавить новую строку [или обновить существующую строку] С значение ключа, что соответствует [другая] существующей строки.

ранее на той же странице руководства указано, что

список столбцов форма (col1,col2,...) создает индекс с несколькими столбцами. Индекс Ключевые значения формируются путем объединения значений заданных столбцов.

как это ограничение реализовано, не документировано, но оно должно как-то приравниваться к предварительному SELECT со значениями, которые необходимо вставить / обновить. Стоимость такой проверки часто ничтожно мала, потому что, по определению, поля индексируются (эта накладная становится актуальной общаясь с большей частью вставки).

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