Mysql int vs varchar jako klucz podstawowy (InnoDB Storage Engine?

13

Tworzę aplikację internetową (system zarządzania projektami) i zastanawiałem się nad tym, jeśli chodzi o wydajność.

Mam tabelę problemów, w której znajduje się 12 kluczy obcych łączących różne tabele. z tych, 8 z nich musiałbym dołączyć, aby uzyskać pole tytułu z innych tabel, aby rekord miał jakikolwiek sens w aplikacji internetowej, ale wtedy oznacza wykonanie 8 złączeń, co wydaje się naprawdę przesadne, zwłaszcza że wciągam tylko 1 pole dla każdego z tych złączeń.

Teraz powiedziano mi również, aby używać automatycznego klucza podstawowego (chyba że sharding jest problemem, w którym to przypadku powinienem użyć GUID) ze względów trwałości, ale jak źle jest używać varchar (maksymalna długość 32) pod względem wydajności? Mam na myśli, że większość tych tabel prawdopodobnie nie będzie miała wielu rekordów (większość z nich powinna mieć mniej niż 20 lat). Również jeśli użyję tego tytułu jako klucza podstawowego, nie będę musiał wykonywać złączeń w 95% przypadków, więc dla 95% sql nawet wystąpiłbym jakikolwiek spadek wydajności (tak myślę). Jedynym minusem, jaki mogę wymyślić, jest to, że będę miał większe wykorzystanie miejsca na dysku (ale minusem jest to naprawdę wielka sprawa).

Powodem, dla którego używam tabel wyszukiwania dla wielu takich rzeczy zamiast wyliczeń jest to, że potrzebuję wszystkich tych wartości, aby użytkownik końcowy mógł je skonfigurować za pośrednictwem samej aplikacji.

Jakie są wady używania varchar jako klucza podstawowego dla tabeli, z wyjątkiem wielu rekordów?

AKTUALIZACJA - Niektóre testy

Postanowiłem więc przeprowadzić podstawowe testy tych rzeczy. Mam 100000 rekordów i to są podstawowe zapytania:

Podstawowe zapytanie 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

Zapytanie bazowe 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

Uruchomiłem te zapytanie z następującymi dodatkami:

  • Wybierz konkretny element (gdzie i.key = 43298)
  • Grupuj według i.id
  • Zamów przez (it.title dla int FK, i.issueTypeId dla varchar FK)
  • Limit (50000, 100)
  • Grupuj i ograniczaj razem
  • Grupuj, porządkuj i ograniczaj razem

Wyniki dla tych, gdzie:

TYP ZAPYTAŃ: VARCHAR FK TIME / INT FK TIME


Zapytanie podstawowe: ~ 4ms / ~ 52ms

Wybierz konkretny element: ~ 140ms / ~ 250ms

Grupuj według identyfikatora i.id: ~ 4ms / ~ 2.8sec

Sortuj według: ~ 231ms / ~ 2sec

Limit: ~ 67ms / ~ 343ms

Grupuj i ogranicz razem: ~ 504ms / ~ 2sec

Grupuj, porządkuj i ograniczaj razem: ~ 504 ms / ~ 2,3 s

Teraz nie wiem, jaką konfigurację mogę zrobić, aby przyspieszyć jedną lub drugą (lub obie), ale wygląda na to, że VARCHAR FK widzi szybciej w zapytaniach o dane (czasem znacznie szybciej).

Chyba muszę wybrać, czy to zwiększenie prędkości jest warte dodatkowego rozmiaru danych / indeksu.

ryanzec
źródło
Twoje testy wskazują coś. Chciałbym również przetestować różne ustawienia InnoDB (pule buforów itp.), Ponieważ domyślne ustawienia MySQL nie są tak naprawdę zoptymalizowane dla InnoDB.
ypercubeᵀᴹ
Należy również przetestować działanie funkcji Wstaw / Aktualizuj / Usuń, ponieważ może to również mieć wpływ na rozmiar indeksu. Kluczem klastrowym każdej tabeli InnoDB jest zwykle PK, a ta kolumna (PK) jest również zawarta w każdym innym indeksie. Jest to prawdopodobnie jeden duży minus dużych PK w InnoDB i wiele indeksów na stole (ale 32 bajty to raczej średni, nie duży, więc może nie być problemem).
ypercubeᵀᴹ
Powinieneś także przetestować większe tabele (w zakresie powiedzmy 10-100 mln wierszy lub więcej), jeśli spodziewacie się, że tabele mogą wzrosnąć powyżej 100 000 (co nie jest tak naprawdę duże).
ypercubeᵀᴹ
@ypercube Zwiększam więc dane do 2 milionów, a instrukcja select dla int FK zwalnia wykładniczo, gdy klucz obcy varchar pozostaje dość stabilny. Pomyśl, że varchar jest wart ceny w wymaganiach dotyczących dysku / pamięci dla zwiększenia wybranych zapytań (co będzie krytyczne dla tej konkretnej tabeli i kilku innych).
ryanzec
Przed wyciągnięciem wniosków po prostu sprawdź ustawienia db (a szczególnie InnoDB). Przy małych tabelach referencyjnych nie spodziewałbym się wykładniczego wzrostu
ypercubeᵀᴹ

Odpowiedzi:

9

W przypadku kluczy podstawowych przestrzegam następujących zasad:

a) Nie powinny mieć żadnego znaczenia biznesowego - powinny być całkowicie niezależne od aplikacji, którą opracowujesz, dlatego wybieram liczbowe automatycznie generowane liczby całkowite. Jeśli jednak potrzebujesz dodatkowych kolumn, aby były unikalne, utwórz unikalne indeksy, które to obsługują

b) Powinien działać w łączeniach - łączenie z varcharami vs liczbami całkowitymi jest około 2x do 3x wolniejsze wraz ze wzrostem długości klucza podstawowego, więc chcesz mieć klucze jako liczby całkowite. Ponieważ wszystkie systemy komputerowe są binarne, podejrzewam, że jego łańcuch jest zmieniany na binarny, a następnie porównywany z innymi, co jest bardzo wolne

c) Użyj najmniejszego możliwego typu danych - jeśli spodziewasz się, że twoja tabela będzie miała bardzo mało kolumn, na przykład 52 stany USA, to użyj najmniejszego możliwego typu, być może, CHAR (2) dla 2-cyfrowego kodu, ale nadal wybrałbym tinyint (128) dla kolumny vs duże int, które mogą wzrosnąć do 2 miliardów

Będziesz miał również problem z kaskadowaniem zmian z kluczy podstawowych do innych tabel, jeśli na przykład zmieni się nazwa projektu (co nie jest rzadkie)

Wybierz sekwencyjne automatyczne zwiększanie liczb całkowitych dla swoich kluczy podstawowych i zyskaj wbudowane usprawnienia, które systemy baz danych zapewniają w przyszłości dla zmian

Stephen Senkomago Musoke
źródło
1
Ciągi nie są zmieniane na binarne; od początku są przechowywane w formacie binarnym. Jak inaczej byłyby przechowywane? Być może myślisz o operacjach, które pozwolą na porównanie bez rozróżniania wielkości liter?
Jon of All Trades
6

W twoich testach nie porównujesz różnicy wydajności kluczy varchar vs int, ale raczej koszt wielu złączeń. Nic dziwnego, że zapytanie 1 tabeli jest szybsze niż łączenie wielu tabel.
Wadą klucza podstawowego varchar jest wzrost wielkości indeksu, jak wskazał atxdba . Nawet jeśli twoja tabela odnośników nie ma żadnych innych indeksów oprócz PK (co jest dość mało prawdopodobne, ale możliwe), każda tabela, do której odwołuje się odnośnik, będzie miała indeks w tej kolumnie.
Kolejną wadą naturalnych kluczy podstawowych jest to, że ich wartość może ulec zmianie, co powoduje wiele kaskadowych aktualizacji. Nie wszystkie RDMS, na przykład Oracle, nawet pozwalająon update cascade. Ogólnie rzecz biorąc, zmiana wartości klucza podstawowego uważana jest za bardzo złą praktykę. Nie chcę powiedzieć, że naturalne klucze podstawowe są zawsze złe; jeśli wartości odnośników są małe i nigdy się nie zmieniają, myślę, że mogą być do zaakceptowania.

Jedną z opcji, którą warto rozważyć, jest wdrożenie widoku zmaterializowanego. Mysql nie obsługuje go bezpośrednio, ale można osiągnąć pożądaną funkcjonalność za pomocą wyzwalaczy w bazowych tabelach. Będziesz miał jeden stół, który ma wszystko, co potrzebujesz do wyświetlenia. Ponadto, jeśli wydajność jest akceptowalna, nie zmagaj się z problemem, który obecnie nie istnieje.

a1ex07
źródło
3

Największym minusem jest powtarzalność PK. Wskazałeś na wzrost wykorzystania miejsca na dysku, ale dla jasności większy rozmiar indeksu jest twoim największym problemem. Ponieważ innodb jest indeksem klastrowym, każdy indeks wtórny wewnętrznie przechowuje kopię PK, której używa do ostatecznego znalezienia pasujących rekordów.

Mówisz, że tabele powinny być „małe” (20 wierszy rzeczywiście jest bardzo małe). Jeśli masz wystarczającą ilość pamięci RAM, aby ustawić innodb_buffer_pool_size równy

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

Zrób to, a prawdopodobnie będziesz ładnie siedzieć. Zasadniczo jednak należy pozostawić co najmniej 30% - 40% całkowitej pamięci systemowej na inne koszty ogólne i dyskietkę mysql. Zakładając, że jest to dedykowany serwer DB. Jeśli masz inne rzeczy działające w systemie, musisz również wziąć pod uwagę ich wymagania.

atxdba
źródło
1

Oprócz odpowiedzi @atxdba - która wyjaśniła ci, dlaczego używanie liczb jest lepsze dla miejsca na dysku, chciałem dodać dwa punkty:

  1. Jeśli twoja tabela problemów jest oparta na VARCHAR FK i powiedzmy, że masz 20 małych VARCHAR (32) FK, twój rekord może osiągnąć długość 20 x 32 bajtów, podczas gdy jak wspomniano, pozostałe tabele są tabelami wyszukiwania, więc INT FK może być TINYINT FK dla 20 pól 20 rekordów bajtów. Wiem, że dla kilkuset rekordów niewiele się to zmieni, ale kiedy dojdziesz do kilku milionów, docenisz oszczędność miejsca

  2. W przypadku problemu z szybkością rozważę użycie indeksów pokrywających, ponieważ wydaje się, że dla tego zapytania nie pobiera się tak dużej ilości danych z tabel odnośników, które wybrałbym dla indeksów indeksujących i jeszcze raz przetestowałem dostarczone przez ciebie VARCHAR FK / W / COVERING INDEKS I regularne INT FK.

Mam nadzieję, że to może pomóc

Spredzy
źródło