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.
źródło
Odpowiedzi:
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
źródło
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.
źródło
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
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.
źródło
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:
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
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
źródło