Zastanawiałem się, jak najlepiej zaimplementować system tagów, taki jak używany w SO. Myślałem o tym, ale nie mogę znaleźć dobrego skalowalnego rozwiązania.
Myślałem o podstawowym rozwiązaniu z trzema stołami: o tags
stole, articles
stołach i tag_to_articles
stole.
Czy to najlepsze rozwiązanie tego problemu, czy też istnieją alternatywy? Korzystając z tej metody, tabela stałaby się bardzo duża w czasie, a wyszukiwanie to nie jest zbyt wydajne, jak zakładam. Z drugiej strony nie jest tak ważne, aby zapytanie było wykonywane szybko.
Odpowiedzi:
Myślę, że ten post na blogu będzie interesujący: Tagi: Schematy baz danych
Rozwiązanie „MySQLicious”
W tym rozwiązaniu schemat ma tylko jedną tabelę, jest on zdenormalizowany. Ten typ jest nazywany „rozwiązaniem MySQLicious”, ponieważ MySQLicious importuje dane del.icio.us do tabeli o takiej strukturze.
Zapytanie o skrzyżowanie (AND) dla „search + webservice + semweb”:
Zapytanie Union (OR) dla „search | webservice | semweb”:
Zapytanie minus dla „search + webservice-semweb”
Rozwiązanie „Scuttle”
Scuttle organizuje swoje dane w dwóch tabelach. Ta tabela „scCategories” jest tabelą „tagów” i ma klucz obcy do tabeli „zakładek”.
Zapytanie o skrzyżowanie (AND) dla „zakładka + usługa internetowa + semweb”:
Najpierw przeszukiwane są wszystkie kombinacje zakładka-znacznik, gdzie tag to „zakładka”, „usługa sieciowa” lub „semweb” (c.category IN („bookmark”, „webservice”, „semweb”)), a następnie tylko zakładki, które uwzględniono wszystkie trzy wyszukiwane tagi (HAVING COUNT (b.bId) = 3).
Zapytanie Union (OR) dla „bookmark | webservice | semweb”: Po prostu pomiń klauzulę HAVING i masz union:
Minus (wykluczenie) Zapytanie dla „bookmark + webservice-semweb”, czyli: bookmark AND webservice AND NOT semweb.
Pominięcie opcji HAVING COUNT prowadzi do zapytania „bookmark | webservice-semweb”.
Rozwiązanie „Toxi”
Toxi wymyślił strukturę z trzema stołami. Za pomocą tabeli „tagmap” zakładki i znaczniki są powiązane n-to-m. Każdy tag może być używany razem z różnymi zakładkami i odwrotnie. Ten schemat DB jest również używany przez wordpress. Zapytania są takie same, jak w przypadku rozwiązania „scuttle”.
Zapytanie o skrzyżowanie (AND) dla „bookmark + webservice + semweb”
Zapytanie Union (OR) dla „bookmark | webservice | semweb”
Minus (wykluczenie) Zapytanie dla „bookmark + webservice-semweb”, czyli: bookmark AND webservice AND NOT semweb.
Pominięcie opcji HAVING COUNT prowadzi do zapytania „bookmark | webservice-semweb”.
źródło
Nie ma nic złego w rozwiązaniu z trzema stołami.
Inną opcją jest ograniczenie liczby tagów, które można zastosować do artykułu (np. 5 w SO) i dodanie ich bezpośrednio do tabeli artykułów.
Normalizacja bazy danych ma swoje zalety i wady, podobnie jak sztywne podłączanie rzeczy do jednej tabeli ma zalety i wady.
Nic nie mówi, że nie możesz zrobić obu. Powtarzanie informacji jest sprzeczne z relacyjnymi paradygmatami DB, ale jeśli celem jest wydajność, być może trzeba będzie złamać te paradygmaty.
źródło
Proponowana przez Ciebie implementacja trzech tabel będzie działać w przypadku tagowania.
Przepełnienie stosu używa jednak innej implementacji. Przechowują tagi w kolumnie varchar w tabeli postów w postaci zwykłego tekstu i używają indeksowania pełnotekstowego, aby pobrać posty pasujące do tagów. Na przykład
posts.tags = "algorithm system tagging best-practices"
. Jestem pewien, że Jeff gdzieś o tym wspomniał, ale nie pamiętam gdzie.źródło
Proponowane rozwiązanie jest najlepszym - jeśli nie jedynym wykonalnym - sposobem, w jaki przychodzi mi do głowy, aby rozwiązać relację wiele do wielu między tagami a artykułami. Więc mój głos jest za „tak, nadal jest najlepszy”. Byłbym jednak zainteresowany jakąkolwiek alternatywą.
źródło
Jeśli twoja baza danych obsługuje tablice indeksowalne (jak na przykład PostgreSQL), poleciłbym rozwiązanie całkowicie zdenormalizowane - przechowywanie znaczników jako tablicy ciągów w tej samej tabeli. Jeśli nie, najlepszym rozwiązaniem jest dodatkowa tabela mapująca obiekty na znaczniki. Jeśli chcesz przechowywać dodatkowe informacje w odniesieniu do tagów, możesz użyć oddzielnej tabeli tagów, ale nie ma sensu wprowadzać drugiego sprzężenia dla każdego wyszukiwania tagów.
źródło
Chciałbym zasugerować zoptymalizowany MySQLicious dla lepszej wydajności. Wcześniej wady rozwiązania Toxi (3 tabele) są
Jeśli masz miliony pytań i każdy ma po 5 tagów, to w tabeli tagmap będzie 5 milionów wpisów. Więc najpierw musimy odfiltrować 10 tysięcy wpisów tagmap na podstawie wyszukiwania tagów, a następnie ponownie odfiltrować pasujące pytania z tych 10 tysięcy. Więc podczas odfiltrowywania, jeśli identyfikator artykułu jest prosty numeryczny, to jest w porządku, ale jeśli jest to rodzaj UUID (32 varchar), to filtrowanie wymaga większego porównania, mimo że jest indeksowane.
Moje rozwiązanie:
Za każdym razem, gdy tworzony jest nowy tag, miej counter ++ (podstawa 10) i przekonwertuj ten licznik na base64. Teraz każda nazwa tagu będzie miała identyfikator base64. i przekaż ten identyfikator do interfejsu użytkownika wraz z nazwą. W ten sposób będziesz mieć maksymalnie dwa identyfikatory znaków, dopóki nie będziemy mieli 4095 tagów utworzonych w naszym systemie. Teraz połącz te wiele tagów w każdej kolumnie tagu tabeli pytań. Dodaj również separator i posortuj go.
A więc stół wygląda tak
Podczas odpytywania zapytaj o identyfikator zamiast prawdziwej nazwy tagu. Od kiedy jest SORTOWANY ,
and
stan na tagu będzie bardziej wydajny (LIKE '%|a|%|c|%|f|%
).Zauważ, że pojedynczy separator spacji nie wystarczy i potrzebujemy podwójnego separatora, aby rozróżnić tagi takie jak
sql
imysql
ponieważLIKE "%sql%"
również zwrócimysql
wyniki. Powinien byćLIKE "%|sql|%"
Wiem, że wyszukiwanie nie jest indeksowane, ale mimo to możesz zindeksować inne kolumny związane z artykułem, takie jak autor / data Czas w przeciwnym razie spowoduje pełne skanowanie tabeli.
Wreszcie, dzięki temu rozwiązaniu nie jest wymagane sprzężenie wewnętrzne, w przypadku gdy trzeba porównać milion rekordów z 5 milionami rekordów pod warunkiem łączenia.
źródło
Uwagi:
AUTO_INCREMENT
PK. Dlatego jest lepszy niż Scuttle.LIKE
z rozszerzeniem wiodącym wieloznacznym; fałszywe trafienia w podciągach)Powiązane dyskusje (dla MySQL):
wiele: wiele uporządkowanych list optymalizacji tabel mapowania
źródło