Mysql против varchar в качестве первичного ключа (InnoDB Storage Engine?

13

Я создаю веб-приложение (систему управления проектами), и мне было интересно об этом, когда дело доходит до производительности.

У меня есть таблица вопросов, внутри которой есть 12 внешних ключей, связанных с различными другими таблицами. из них 8 из них мне нужно было бы объединить, чтобы получить поле заголовка из других таблиц, чтобы запись имела какой-либо смысл в веб-приложении, но тогда это означает выполнение 8 объединений, что кажется чрезмерным, особенно если учесть, что я только вытягиваю 1 поле для каждого из этих объединений.

Теперь мне также сказали использовать автоинкрементный первичный ключ (если не нужно использовать шардинг, в этом случае я должен использовать GUID) по причинам постоянства, но насколько плохо использовать varchar (максимальная длина 32) с точки зрения производительности? Я имею в виду, что большинство этих таблиц, вероятно, не будет иметь много записей (большинство из них должно быть меньше 20). Кроме того, если я использую заголовок в качестве первичного ключа, мне не нужно будет выполнять объединения в 95% случаев, поэтому для 95% sql я бы даже столкнулся с какой-либо потерей производительности (я думаю). Единственный недостаток, о котором я могу подумать, - это то, что у меня будет больше использования дискового пространства (но один день - это действительно большая проблема).

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

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

ОБНОВЛЕНИЕ - Некоторые тесты

Поэтому я решил сделать некоторые базовые тесты на этот материал. У меня есть 100000 записей, и это базовые запросы:

База VARCHAR FK Query

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

База INT FK Query

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

Я также выполнил этот запрос со следующими дополнениями:

  • Выберите конкретный элемент (где i.key = 43298)
  • Группировать по i.id
  • Упорядочить по (it.title для int FK, i.issueTypeId для varchar FK)
  • Лимит (50000, 100)
  • Группируйте и ограничивайте вместе
  • Группируйте, заказывайте и ограничивайте вместе

Результаты для них, где:

ТИП ЗАПРОСА: VARCHAR FK TIME / INT FK TIME


Базовый запрос: ~ 4 мс / ~ 52 мс

Выберите конкретный элемент: ~ 140 мс / ~ 250 мс

Группировка по i.id: ~ 4 мс / ~ 2,8 с

Упорядочить по: ~ 231мс / ~ 2сек

Предел: ~ 67мс / ~ 343мс

Группировать и ограничивать вместе: ~ 504мс / ~ 2сек

Группировать, заказывать и ограничивать вместе: ~ 504ms /~2.3sec

Теперь я не знаю, какую конфигурацию я мог бы сделать, чтобы сделать один или другой (или оба) быстрее, но кажется, что VARCHAR FK видит быстрее в запросах данных (иногда намного быстрее).

Я думаю, мне нужно выбрать, стоит ли это повышение скорости дополнительным размером данных / индекса.

ryanzec
источник
Ваше тестирование указывает на что-то. Я также протестировал бы с различными настройками InnoDB (пулы буферов и т. Д.), Потому что настройки MySQL по умолчанию не оптимизированы для InnoDB.
ypercubeᵀᴹ
Вы также должны проверить производительность Вставить / Обновить / Удалить, так как это может также зависеть от размера индекса. Один кластеризованный ключ каждой таблицы InnoDB обычно представляет собой PK, и этот (PK) столбец также включается во все остальные индексы. Это, вероятно, один большой недостаток больших PK в InnoDB и большого количества индексов в таблице (но 32 байта довольно средние, а не большие, поэтому это не может быть проблемой).
ypercubeᵀᴹ
Вам также следует протестировать с большими таблицами (в диапазоне, скажем, 10-100M строк или больше), если вы ожидаете, что ваши таблицы могут вырасти выше 100К (что не очень большой).
ypercubeᵀᴹ
@ypercube Таким образом, я увеличиваю данные до 2 миллионов, и оператор выбора для int FK экспоненциально замедляется, когда внешний ключ varchar остается довольно устойчивым. Подумайте, что varchar стоит своей цены в требованиях к диску / памяти для выигрыша в отдельных запросах (что будет критично для этой конкретной таблицы и некоторых других).
ryanzec
Просто проверьте настройки вашей БД (и особенно InnoDB), прежде чем делать выводы. С небольшими справочными таблицами я бы не ожидал экспоненциального увеличения
ypercubeᵀᴹ

Ответы:

9

Я следую следующим правилам для первичных ключей:

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

б) Должен выполняться в соединениях - соединение с varchars против целых чисел примерно в 2–3 раза медленнее по мере увеличения длины первичного ключа, поэтому вы хотите, чтобы ваши ключи были целыми числами. Поскольку все компьютерные системы являются бинарными, я подозреваю, что из-за того, что строка заменена на двоичную, она сравнивается с другими, что очень медленно.

c) Используйте наименьший возможный тип данных - если вы ожидаете, что в вашей таблице будет очень мало столбцов, скажем, 52 штатов США, то используйте наименьший возможный тип, возможно, CHAR (2) для двухзначного кода, но я все равно выбрал бы tinyint (128) для столбца против большого int, который может доходить до 2 миллиардов

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

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

Стивен Сенкомаго Мусоке
источник
1
Строки не изменяются на двоичные; они хранятся в двоичном виде с самого начала. Как еще они будут храниться? Возможно, вы думаете об операциях, позволяющих проводить сравнение без учета регистра?
Джон на все руки
6

В ваших тестах вы сравниваете не разницу между производительностью ключей varchar и int, а стоимость нескольких соединений. Не удивительно, что запрос к 1 таблице быстрее, чем объединение многих таблиц.
Одним из недостатков первичного ключа varchar является увеличение размера индекса, как указывает atxdba . Даже если ваша таблица поиска не имеет других индексов, кроме PK (что маловероятно, но возможно), у каждой таблицы, которая ссылается на поиск, будет индекс по этому столбцу.
Еще одна плохая вещь о естественных первичных ключах, это то, что их значение может измениться, что вызывает множество каскадных обновлений. Не все RDMS, например Oracle, даже позволяютon update cascade, В целом, изменение значения первичного ключа считается очень плохой практикой. Я не хочу сказать, что естественные первичные ключи всегда злы; если значения поиска малы и никогда не меняются, я думаю, что это может быть приемлемо.

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

a1ex07
источник
3

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

Вы говорите, что таблицы должны быть «маленькими» (20 строк действительно очень маленькие). Если у вас достаточно оперативной памяти, чтобы установить innodb_buffer_pool_size равным

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Тогда сделайте это, и вы, вероятно, будете сидеть красиво. Как правило, хотя вы хотите оставить не менее 30% - 40% общей системной памяти для других издержек MySQL и дискового кэша. И это при условии, что это выделенный сервер БД. Если в вашей системе работают другие компоненты, вам также необходимо учитывать их требования.

atxdba
источник
1

В дополнение к ответу @atxdba, который объяснил вам, почему числовое использование было бы лучше для дискового пространства, я хотел бы добавить два пункта:

  1. Если ваша таблица Issues основана на VARCHAR FK, и, скажем, у вас есть 20 маленьких VARCHAR (32) FK, ваша запись может достигать длины 20x32 байт, в то время как другие таблицы представляют собой таблицы поиска, поэтому INT FK может быть TINYINT FK, который делает для 20 полей по 20 байт записей. Я знаю, что для нескольких сотен записей это не сильно изменится, но когда вы достигнете нескольких миллионов, я думаю, вы по достоинству оцените экономию места

  2. Что касается скорости, я бы подумал об использовании покрывающих индексов, так как для этого запроса кажется, что вы не извлекаете столько данных из справочных таблиц, я бы пошел для покрытия указателей и еще раз проверил бы ваш файл, предоставленный VARCHAR FK / W / COVERING ИНДЕКС И обычные INT FK.

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

Spredzy
источник