Mysql int vs varchar як первинний ключ (InnoDB Storage Engine?


13

Я будую веб-додаток (систему управління проектами) і мені було цікаво про це, коли справа доходить до продуктивності.

У мене є таблиця "Випуски", всередині неї - 12 сторонніх ключів, що посилаються на різні інші таблиці. з них, 8 з них, мені потрібно було б приєднатись, щоб отримати поле заголовка з інших таблиць, щоб запис мав сенс у веб-додатку, але тоді означає робити 8 приєднань, що здається надмірно тим більше, оскільки я лише підтягую 1 поле для кожного з них приєднується.

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

Причиною того, що я використовую таблиці пошуку для багатьох цих матеріалів замість перерахунків, є те, що мені потрібно, щоб усі ці значення були налаштовані кінцевим користувачем через саме додаток.

Які недоліки використання varchar в якості основного ключа для таблиці, окрім багатьох записів?

ОНОВЛЕННЯ - Деякі тести

Тому я вирішив зробити деякі основні тести на цей матеріал. У мене є 100000 записів, і це базові запити:

База VARCHAR FK Запит

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 Запит

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


Базовий запит: ~ 4ms / ~ 52ms

Виберіть конкретний предмет: ~ 140ms / ~ 250ms

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

Упорядкувати за: ~ 231ms / ~ 2sec

Ліміт: ~ 67ms / ~ 343ms

Групуйте та обмежуйте разом: ~ 504ms / ~ 2sec

Групуйте, замовляйте та обмежуйте разом: ~ 504ms /~2.3sec

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

Я думаю, що мені доведеться вибрати, чи варто це підвищення швидкості додаткових даних / розміру індексу.


Ваше тестування щось вказує. Я також би протестував з різними налаштуваннями InnoDB (буферні пули тощо), оскільки налаштування MySQL за замовчуванням не дуже оптимізовані для InnoDB.
ypercubeᵀᴹ

Ви також повинні протестувати продукти Вставити / Оновити / Видалити, оскільки на це може вплинути і розмір індексу. Один кластерний ключ кожної таблиці InnoDB, як правило, PK, і цей (PK) стовпець також включений у будь-який інший індекс. Це, мабуть, один великий мінус великих ПК у InnoDB та багатьох індексів на столі (але 32 байти - це досить середній, не великий, тому це може не бути проблемою).
ypercubeᵀᴹ

Ви також повинні протестувати з більшими таблицями (в діапазоні скажімо 10-100 М рядків або більше), якщо ви очікуєте, що ваші таблиці можуть вирости вище 100 К (що насправді не велике).
ypercubeᵀᴹ

@ypercube Отже, я збільшую дані до 2 мільйонів, і оператор select для int FK стає повільніше експоненціально, коли зовнішній ключ varchar залишається досить стійким. Думаю, що варшар вартує ціни в вимогах до диска / пам'яті для посилення в обраних запитах (що стане критичним для цієї конкретної таблиці та кількох інших).
ryanzec

Перед тим, як робити висновки, просто перевірте свої налаштування db (особливо InnoDB). З невеликими довідковими таблицями я не очікував би експоненціального збільшення
ypercubeᵀᴹ

Відповіді:


9

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

a) Не повинні мати жодного ділового значення - вони повинні бути повністю незалежними від програми, яку ви розробляєте, тому я хочу використовувати числові цілі числа, створені автоматично. Однак якщо вам потрібні додаткові стовпці, щоб бути унікальними, тоді створіть унікальні індекси, які це підтримують

б) повинен виконувати приєднання - приєднання до варшарів проти цілих чисел приблизно в 2–3 рази повільніше в міру зростання довжини первинного ключа, тому ви хочете мати свої ключі як цілі числа. Оскільки всі комп'ютерні системи є бінарними, я підозрюю, що його coz рядок змінюється на двійковий, а потім порівняно з іншими, що дуже повільно

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

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

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


1
Рядки не змінені на двійкові; вони зберігаються у двійковій формі з самого початку. Як інакше вони будуть зберігатися? Можливо, ви думаєте про операції, щоб дозволити порівняння з урахуванням регістру?
Йон усіх торгів

6

У ваших тестах ви не порівнюєте різницю продуктивності клавіш varchar vs int, а швидше багаторазове приєднання. Не дивно, що запит на 1 таблицю швидше, ніж приєднання до багатьох таблиць.
Одним із недоліків первинного ключа varchar є збільшення розміру індексу, як вказував atxdba . Навіть якщо у вашій таблиці пошуку немає інших індексів, крім PK (що малоймовірно, але можливо), кожна таблиця, на яку посилається пошук, матиме індекс у цьому стовпці.
Ще одна погана річ щодо природних первинних ключів - це те, що їх значення може змінюватися, що спричиняє безліч каскадних оновлень. Не всі RDMS, наприклад, Oracle, навіть дозволяють вамon update cascade. Взагалі, зміна значення первинного ключа вважається дуже поганою практикою. Я не хочу сказати, що природні первинні ключі - це завжди зло; якщо значення пошуку невеликі і ніколи не змінюються, я думаю, це може бути прийнятним.

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


3

Найбільшим недоліком є ​​повторення ПК. Ви вказали на збільшення використання дискового простору, але для того, щоб зрозуміти, збільшений розмір індексу викликає найбільше занепокоєння. Оскільки innodb - це кластерний індекс, кожен вторинний індекс внутрішньо зберігає копію ПК, яку він використовує, щоб у кінцевому підсумку знайти відповідні записи.

Ви кажете, що очікується, що таблиці будуть "маленькими" (20 рядків дійсно дуже крихітні). Якщо у вас достатньо оперативної пам’яті, щоб встановити розмір innodb_buffer_pool_size рівний

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

Тоді зробіть це, і ви, мабуть, сидите симпатично. Як правило, хоча ви хочете залишити щонайменше 30% - 40% загальної системної пам'яті для інших mysql накладних та дискових кешів. І це припускаючи, що це виділений сервер БД. Якщо у вас є інші речі, що працюють у системі, вам також потрібно врахувати їх вимоги.


1

На додаток до відповіді @atxdba - яка пояснила, чому краще використовувати числовий для дискового простору, я хотів додати два пункти:

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

  2. Що стосується питання швидкості, я б розглядав можливість використання індексів покриття, оскільки для цього запиту ви не отримуєте такої кількості даних із таблиць пошуку, я б пішов на те, щоб покрити індекс і ще раз зробити тест, наданий вами VARCHAR FK / W / COVERING ІНДЕКС І регулярний INT FK.

Сподіваюся, це може допомогти,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.