Производительность UUID в MySQL?

86

Мы рассматриваем возможность использования значений UUID в качестве первичных ключей для нашей базы данных MySQL. Вставляемые данные генерируются с десятков, сотен или даже тысяч удаленных компьютеров и вставляются со скоростью 100-40 000 вставок в секунду, и мы никогда не будем делать никаких обновлений.

Сама база данных обычно достигает около 50 миллионов записей, прежде чем мы начнем отбирать данные, так что это не массивная база данных, но и не маленькая. Мы также планируем работать на InnoDB, хотя мы готовы изменить это, если есть лучший движок для того, что мы делаем.

Мы были готовы использовать UUID Java Type 4, но во время тестирования наблюдали странное поведение. Во-первых, мы сохраняем как varchar (36), и теперь я понимаю, что нам было бы лучше использовать binary (16) - хотя насколько лучше, я не уверен.

Более важный вопрос: насколько сильно эти случайные данные портят индекс, когда у нас есть 50 миллионов записей? Было бы лучше, если бы мы использовали, например, UUID типа 1, где крайние левые биты были отмечены временем? Или, может быть, нам следует полностью отказаться от UUID и рассмотреть первичные ключи auto_increment?

Я ищу общие мысли / советы по производительности различных типов UUID, когда они хранятся как индекс / первичный ключ в MySQL. Благодаря!

Патрик Лайтбоди
источник
2
отсутствует одна важная деталь: первичные ключи генерируются сервером регистрации или самими клиентскими машинами?
1
@hop, они генерируются 10-1000 клиентами, которые вставляют данные
Патрик Лайтбоди,
Где вам нужна универсальная уникальность в вашем сценарии? Мой совет - придерживаться auto_increment и использовать отдельное поле для описания удаленного компьютера, который отправляет данные. Здесь не нужно изобретать велосипед.
Theodore Zographos

Ответы:

36

UUID - это универсальный уникальный идентификатор. Это универсальная часть, которую вы должны здесь учитывать.

Вам действительно нужно, чтобы идентификаторы были универсально уникальными? Если так, то UUID может быть вашим единственным выбором.

Я настоятельно рекомендую, если вы действительно используете UUID, вы храните их как числа, а не как строку. Если у вас более 50 миллионов записей, то экономия места для хранения улучшит вашу производительность (хотя я не могу сказать насколько).

Если ваши идентификаторы не должны быть универсально уникальными, я не думаю, что вы можете добиться большего, чем просто использовать auto_increment, который гарантирует, что идентификаторы будут уникальными в таблице (поскольку значение будет увеличиваться каждый раз)

Dancrumb
источник
2
Интересный момент; это позволит распараллелить генерацию ключей. Я считаю, что это повысит производительность генерации ключей. Однако вы выбираете производительность INSERT вместо производительности SELECT, если вы используете VARCHAR для хранения UUID. Вам определенно следует выбрать VARBINARY для хранения, чтобы обеспечить производительность SELECT. Дополнительный шаг может повлиять на производительность INSERT, но вы окупитесь улучшением производительности SELECT.
Dancrumb 02
12
В итоге мы провели сравнительный анализ реальных данных, и идентификаторы GUID без ключей были довольно быстрыми, идентификаторы GUID с ключами были ужасными (даже при сохранении как BINARY), а int w / AUTO_COMPLETE был самым быстрым. Я думаю, что в нашем случае нам действительно не хватало леса на деревьях, поскольку генерация последовательности казалась несущественной по сравнению с затратами на хранение большего количества данных + наличие действительно дрянного BTREE из-за случайности GUID
Патрик Лайтбоди,
1
хранить как число означает хранить в двоичном формате? но двоичный формат не читается человеком. Это медленно из-за больших байтов первичного ключа uuid? Если это так, то я мог бы сохранить автоинкремент с другим столбцом для uuid. Тогда производительность не пострадает. Я прав?
Chamnap
4
Строго говоря, UUID универсально уникален, а это означает, что он никогда не появится больше нигде в мире. Вам это нужно только в том случае, если вы публикуете свои данные публично. Что касается хранения UUID в виде числа, я не имею в виду binaryформат. Я имею в виду 128-битное число, а не 288-битную строку. Например, слово «привет» в ASCII - 68 65 6C 6C 6Fэто число 448 378 203 247. Для хранения строки «68656C6C6F» требуется 10 байтов. Для числа 448 378 203 247 требуется только 5. В общем, если вам действительно не нужен первый U в UUID, вы не сможете добиться большего, чемauto_increment
Dancrumb
1
@Chamnap: Предлагаю вам задать вопрос о переполнении стека: o)
Dancrumb
78

На моей работе мы используем UUID как PK. По опыту могу сказать, что НЕ ИСПОЛЬЗУЙТЕ ИХ как ПК (кстати, SQL Server).

Это одна из тех вещей, когда у вас меньше 1000 записей, это нормально, но когда у вас миллионы, это худшее, что вы можете сделать. Почему? Поскольку UUID не являются последовательными, поэтому каждый раз, когда вставляется новая запись, MSSQL должен переходить на правильную страницу для вставки записи, а затем вставлять запись. Действительно неприятным последствием этого является то, что все страницы оказываются разного размера и оказываются фрагментированными, поэтому теперь мы должны периодически выполнять дефрагментацию.

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

Однако большим преимуществом использования UUID в качестве PK является то, что если у нас есть кластеры БД, при слиянии не будет конфликтов.

Я бы порекомендовал следующую модель: 1. PK INT Identity 2. Дополнительный столбец автоматически генерируется как UUID.

Таким образом, возможен процесс слияния (UUID будет вашим НАСТОЯЩИМ ключом, а PK будет чем-то временным, что дает вам хорошую производительность).

ПРИМЕЧАНИЕ. Лучшее решение - использовать NEWSEQUENTIALID (как я уже говорил в комментариях), но для устаревшего приложения, у которого мало времени на рефакторинг (и, что еще хуже, не контролирует все вставки), это невозможно сделать. Но на самом деле, по состоянию на 2017 год, я бы сказал, что лучшим решением здесь является NEWSEQUENTIALID или выполнение Guid.Comb с NHibernate.

Надеюсь это поможет

Кэт Лим Руис
источник
Я действительно не знаю, что означают эти термины, но дело в том, что индексы нужно переиндексировать каждый месяц. Если то, что вы упомянули, устраняет задачу переиндексации, я не знаю, но могу спросить.
Кэт Лим Руис
3
Я думал о том, что это может не сработать для отношений между родителями и детьми. В этом случае, я думаю, вам нужно добавить в дочернюю таблицу: parent-pk, parent-guid. В противном случае вы можете потерять ссылки между базами данных. Я не думал об этом слишком много и не приводил никаких примеров, но это может быть необходимо
Кэт Лим Руис
4
@KatLimRuiz на сервере sql вы можете использовать NEWSEQUENTIALID () technet.microsoft.com/en-us/library/ms189786.aspx, чтобы избежать проблем с производительностью
giammin
Действительно, но NEWSEQUENTIALID работает только как DEFAULT. Поэтому вам нужно разработать весь свой DAL вокруг этого, что нормально для новых проектов, но не так просто для большого наследия
Кэт Лим Руис
@KatLimRuiz гений. Это отличный компромисс
jmgunn87
26

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

На производительность, кратко :

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

На своем битовом уровне UUID составляет 128 бит, что означает, что он умещается в 16 байтов, обратите внимание, что это не очень удобно для чтения человеком, но он сохранит низкий объем памяти и всего в 4 раза больше, чем 32-битное int, или 2 раз больше, чем 64-битное int. Я буду использовать VARBINARY (16) Теоретически это может работать без особых накладных расходов.

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

Я считаю, что они оба отвечают на ваш вопрос.

Кайл Розендо
источник
2
На самом деле, я прочитал обе эти статьи до того, как опубликовать этот вопрос, и у меня все еще не было хорошего ответа. Например, никто не говорит о UUIDS типа 1 и типа 4 :(
Патрик Лайтбоди,
Честно говоря, я немного обновил свой ответ. Однако я не думаю, что это дает слишком много дополнительной информации.
Кайл Розендо,
@ Патрик: вы задали слишком много разных тем в своем вопросе.
1
9 лет спустя, но следует также отметить для потомков, что, в отличие от целочисленных идентификаторов, приложения могут безопасно генерировать UUID, полностью удаляя генерацию из базы данных. Манипулирование UUID для оптимизации производительности (на основе временных меток, но модифицированных таким образом, чтобы их можно было наивно отсортировать) заметно проще практически на любом языке, кроме SQL. К счастью, сегодня почти все базы данных (включая MySQL) обрабатывают первичные ключи UUID намного лучше, чем раньше.
Майлз Элам
5

Я стараюсь избегать UUID просто потому, что его сложно хранить и использовать в качестве первичного ключа, но есть преимущества. Главное - они УНИКАЛЬНЫЕ.

Обычно я решаю проблему и избегаю UUID, используя поля с двумя ключами.

КОЛЛЕКТОР = УНИКАЛЬНЫЙ НАЗНАЧЕН ДЛЯ МАШИНЫ

ID = ЗАПИСЬ, СОБРАННАЯ КОЛЛЕКТОРОМ (поле auto_inc)

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

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

Я только что видел слишком много падений производительности при использовании UUID. Они чувствуют себя обманщиком ...

Гленн Дж. Шворак
источник
3

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

Сервер ключей поддерживает следующий доступный идентификатор

  • Сервер 1 запрашивает блок идентификатора.
  • Сервер ключей возвращает (1,1000).
    Сервер 1 может вставить 1000 записей, пока ему не потребуется запросить новый блок.
  • Сервер 2 запрашивает индексный блок.
  • Сервер ключей возвращает (1001,2000)
  • и т.д...

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

Буке Верстех
источник
Интересное предположение в теории. На практике это было бы сложно осуществить. Более практичным решением, вероятно, был бы ответ, предложенный Швораком.
Саймон Ист
2

Я бы назначил каждому серверу числовой идентификатор транзакционным способом. Затем каждая вставленная запись будет просто автоматически увеличивать свой собственный счетчик. Комбинация ServerID и RecordID будет уникальной. Поле ServerID можно проиндексировать, и будущая производительность выбора на основе ServerID (при необходимости) может быть намного лучше.

Николай
источник
2

Короткий ответ заключается в том, что у многих баз данных есть проблемы с производительностью (в частности, с большими объемами INSERT) из-за конфликта между их методом индексирования и преднамеренной энтропией UUID в старших битах. Есть несколько распространенных хаков:

  • выберите другой тип индекса (например, некластеризованный на MSSQL), который не против
  • изменить данные, чтобы переместить энтропию в биты более низкого порядка (например, переупорядочить байты UUID V1 в MySQL)
  • сделать UUID вторичным ключом с автоинкрементом первичного ключа int

... но это все хаки - и, наверное, хрупкие.

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

Стивенс
источник
1

А как насчет какого-нибудь UID, созданного вручную? Дайте каждому из тысяч серверов идентификатор и сделайте первичный ключ комбинированным ключом автоинкремента, MachineID ???

MindStalker
источник
Я подумал об этом, и, возможно, потребуется выполнить несколько тестов. Даже временной локальной последовательности на каждой из 1000 машин в сочетании с отметкой времени может быть достаточно. Пример: machine_id + temp_seq + timestamp
Патрик Лайтбоди,
Возможно ли иметь temp_sequence, которая сбрасывается каждый тик отметки времени? Я не уверен.
MindStalker 02
1

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

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

То же самое касается varchar (char, на самом деле) и двоичного: это может только помочь. Неужели важно, насколько улучшена производительность?


источник
0

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

В своем исследовании я нашел этот довольно интересный пост о производительности:

утверждая, что из-за случайности индексов GUID / UUID деревья могут стать довольно несбалансированными. в базе знаний MariaDB я нашел еще одно сообщение, предлагающее решение. Но с тех пор как новый UUID_TO_BIN об этом позаботится . Эта функция доступна только в MySQL (протестированная версия 8.0.18), но не в MariaDB (версия 10.4.10).

TL; DR: сохранить UUID как преобразованные / оптимизированные значения BINARY (16).

theking2
источник