Spec-Zone.ru › MariaDB

Производительность GUID/UUID

Проблема

GUID/UUID (Globally/Universally Unique Identifiers) генерируются случайным образом. Поэтому вставка в индекс означает много перемещений. Как только индекс становится слишком большим для кеширования, большинство вставок приводит к обращению к диску. Даже на мощной системе это ограничивает вас несколькими сотнями вставок в секунду.

Функция UUID MariaDB.

Эта проблема в основном решена в MySQL 8.0 с появлением следующей функции: UUID_TO_BIN(str, swap_flag).

Почему это проблема

«Стандартный» GUID/UUID состоит из времени, идентификатора машины и некоторых других данных. Комбинация должна быть уникальной даже без координации между различными компьютерами, которые могут генерировать UUID одновременно.

Верхняя часть GUID/UUID — это нижняя часть текущего времени. Верхняя часть — основная часть для размещения значения в упорядоченном списке (ИНДЕКС). Она циклически повторяется примерно каждые 7,16 минут.

Некоторые математические выкладки... Если индекс достаточно мал, чтобы поместиться в оперативной памяти, каждая вставка в индекс выполняется только процессором, причём записи откладываются и объединяются. Если индекс в 20 раз больше, чем может быть помещено в кеш, то 19 из 20 вставок будут промахами кеша. (Эта математика применима к любому «случайному» индексу.)

Вторая проблема

36 символов — много. Если вы используете его в качестве первичного ключа в InnoDB и имеете вторичные ключи, помните, что каждый вторичный ключ имеет неявную копию первичного ключа, что делает его громоздким.

Искушение заключается в объявлении UUID как VARCHAR(36). И, поскольку вы, вероятно, думаете о глобальном применении, у вас есть CHARACTER SET utf8 (или utf8mb4). Для utf8:

  • 2 — Надстройка для VAR
  • 36 — символов
  • 3 (или 4) байта на символ для utf8 (или utf8mb4). Таким образом, максимальная длина = 2+3*36 = 110 (или 146) байт. Для временных таблиц 108 (или 144) фактически используется, если используется таблица MEMORY.

Для сжатия

  • utf8 не нужен (достаточно ascii), но это отменяется следующими двумя шагами
  • Убрать дефисы
  • UNHEX Теперь он поместится в 16 байт: BINARY(16)

Объединение проблем и разработка решения

Но сначала предупреждение. Это решение работает только для "временных" / "версии 1" UUID. Их можно распознать по «1» в начале третьей группы.

Пример из руководства: 6ccd780c-baba-1026-9564-0040f4311e29. Более современное значение (через несколько лет): 49ea2de3-17a2-11e2-8346-001eecac3efa. Обратите внимание, как третья часть постепенно меняется со временем? Давайте перестроим данные, таким образом:

      1026-baba-6ccd780c-9564-0040f4311e29
      11e2-17a2-49ea2de3-8346-001eecac3efa
      11e2-17ac-106762a5-8346-001eecac3efa -- after a few more minutes

Теперь у нас есть число, которое плавно увеличивается со временем. Несколько источников не будут идеально согласовываться по времени, но они будут близки. «Горячая» точка вставки в INDEX(uuid) будет довольно узкой, что сделает её довольно кешируемой и эффективной.

Если ваши запросы SELECT обычно обращаются к «недавним» UUID, то они тоже будут легко кешироваться. Если же ваши запросы SELECT часто обращаются к старым UUID, они будут случайными и плохо кешироваться. Тем не менее, улучшение вставок поможет системе в целом.

Код для выполнения

Давайте создадим функции хранилища для выполнения трудоёмких действий:

  • Переупорядочить поля
  • Преобразование в/из BINARY(16)
    DELIMITER //

    CREATE FUNCTION UuidToBin(_uuid BINARY(36))
        RETURNS BINARY(16)
        LANGUAGE SQL  DETERMINISTIC  CONTAINS SQL  SQL SECURITY INVOKER
    RETURN
        UNHEX(CONCAT(
            SUBSTR(_uuid, 15, 4),
            SUBSTR(_uuid, 10, 4),
            SUBSTR(_uuid,  1, 8),
            SUBSTR(_uuid, 20, 4),
            SUBSTR(_uuid, 25) ));
    //
    CREATE FUNCTION UuidFromBin(_bin BINARY(16))
        RETURNS BINARY(36)
        LANGUAGE SQL  DETERMINISTIC  CONTAINS SQL  SQL SECURITY INVOKER
    RETURN
        LCASE(CONCAT_WS('-',
            HEX(SUBSTR(_bin,  5, 4)),
            HEX(SUBSTR(_bin,  3, 2)),
            HEX(SUBSTR(_bin,  1, 2)),
            HEX(SUBSTR(_bin,  9, 2)),
            HEX(SUBSTR(_bin, 11))
                 ));

    //
    DELIMITER ;

Затем вы можете делать такие вещи:

    -- Letting MySQL create the UUID:
    INSERT INTO t (uuid, ...) VALUES (UuidToBin(UUID()), ...);

    -- Creating the UUID elsewhere:
    INSERT INTO t (uuid, ...) VALUES (UuidToBin(?), ...);

    -- Retrieving (point query using uuid):
    SELECT ... FROM t WHERE uuid = UuidToBin(?);

    -- Retrieving (other):
    SELECT UuidFromBin(uuid), ... FROM t ...;

Не меняйте WHERE-условие; это будет неэффективным, так как не будет использоваться INDEX(uuid):

    WHERE UuidFromBin(uuid) = '1026-baba-6ccd780c-9564-0040f4311e29' -- NO

TokuDB

TokuDB был устаревшим его разработчиком. Он отключён с MariaDB 10.5 и был удалён в MariaDB 10.6 — MDEV-19780. Рекомендуется MyRocks в качестве долгосрочного пути миграции.

TokuDB — жизнеспособный движок, если вам нужны UUID (даже не версии 1) в большой таблице. TokuDB доступен в MariaDB как стандартный движок, что значительно упрощает его использование. Существует небольшое количество различий между InnoDB и TokuDB; я не буду останавливаться на них здесь.

Tokudb с его стратегией «фрактального» индексирования строит индексы поэтапно. В отличие от InnoDB, индексы в InnoDB вставляются «немедленно» — на самом деле это индексирование буферизуется большей частью размера buffer_pool. Подробнее...

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

  • Проверка ограничений уникальности
  • Получение блока B-дерева (обычно 16 КБ), который должен содержать строку (на основе первичного ключа).
  • Вставка строки (переполнение обычно происходит в 1% случаев; это приводит к разделению блока).
  • Оставить страницу «грязной» в buffer_pool, надеясь, что добавятся ещё строки, прежде чем она будет вытеснена из кеша (buffer_pool). Обратите внимание, что для AUTO_INCREMENT и первичных ключей на основе TIMESTAMP последняя строка в данных будет обновляться многократно перед разделением, следовательно, эта отложенная запись значительно повышает эффективность. С другой стороны, UUID будет очень случайным; когда таблица достаточно большая, блок почти всегда будет выброшен в кэш, прежде чем произойдёт вторая вставка в этот блок. <– В этом и заключается неэффективность UUID. Теперь для вторичных ключей:
  • Все шаги те же, поскольку индекс по сути является «таблицей», за исключением того, что «данные» — это копия первичного ключа.
  • Немедленно требуется проверка уникальности — откладывать чтение нельзя.
  • Есть (я думаю) некоторые другие «задержки», которые избегают некоторого ввода-вывода.

Tokudb, с другой стороны, делает примерно так:

  • Запись частично отсортированных записей данных/индекса на диск, прежде чем точно определить, куда они должны быть размещены.
  • В фоновом режиме объединить эти частично обработаны блоки. Повторять по мере необходимости.
  • В конечном итоге переместить информацию в реальную таблицу/индексы.

Если вы знакомы с тем, как работает сортировка-слияние, сравните это с TokuDB. Каждая «сортировка» выполняет некоторую работу по упорядочиванию; каждое «слияние» очень эффективно.

Резюме:

  • В крайнем случае (данные/индекс намного больше, чем buffer_pool), InnoDB должен читать-изменять-записывать один блок диска размером 16 КБ для каждой записи UUID.
  • Tokudb позволяет объединять несколько UUID в каждый блок диска для каждого ввода-вывода. (Да, Toku перечитывает блоки, но в конечном счёте это выгоднее).
  • Tokudb превосходит, когда таблица очень большая, что подразумевает высокую скорость поступления данных.

Заключение

Это показывает три способа ускорения использования GUID/UUID:

  • Уменьшить размер (меньше → больше кешируемость → быстрее).
  • Переупорядочить UUID, чтобы создать «горячую точку» для улучшения кешируемости.
  • Использование TokuDB (MyRocks имеет схожие архитектурные черты, которые также могут быть полезны для обработки UUID, но это гипотеза и не проверялось).

Обратите внимание, что преимущества «горячей точки» частичны:

  • Преимущества есть для вставленных в хронологическом (или приблизительно хронологическом) порядке, случайные не имеют преимуществ.
  • SELECT/UPDATE по «недавним» UUID имеют преимущество; старые не имеют преимущества.

Postlog

Спасибо Трею за некоторые идеи.

Советы в этом документе применимы к MySQL, MariaDB и Percona.

Написано в окт. 2012 г. Добавлено TokuDB, янв. 2015 г.

См. также

  • Тип данных UUID
  • Подробное обсуждение индексирования UUID
  • Графическое представление случайного характера UUID в первичном ключе
  • Тесты производительности и т. д. от Картика Аппигатлы
  • Более подробная информация о часах
  • Тесты производительности Percona
  • NHibernate может генерировать последовательные GUID, но, похоже, в обратном порядке.

Рик Джеймс любезно разрешил нам использовать эту статью в базе знаний.

Сайт Рика Джеймса содержит дополнительные полезные советы, руководства, оптимизации и советы по отладке.

Исходный источник: http://mysql.rjweb.org/doc.php/uuid

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

© 2023 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/guiduuid-performance/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API