So implementieren Sie das Tag -System
Frage
Ich habe mich gefragt, wie es am besten ist, ein Tag -System zu implementieren, wie das, auf dem sie verwendet wurden. Ich habe darüber nachgedacht, aber ich kann keine gute skalierbare Lösung finden.
Ich dachte darüber nach, eine grundlegende 3 -Table -Lösung zu haben: a tags
Tabelle, an articles
Tische und a tag_to_articles
Tisch.
Ist dies die beste Lösung für dieses Problem oder gibt es Alternativen? Mit dieser Methode würde die Tabelle rechtzeitig extrem groß werden, und für die Suche ist ich nicht allzu effizient. Andererseits ist es nicht so wichtig, dass die Abfrage schnell ausgeführt wird.
Lösung
Ich glaube, Sie werden diesen Blog -Beitrag interessant finden: Tags: Datenbankschemata
Das Problem: Sie möchten ein Datenbankschema haben, in dem Sie ein Lesezeichen (oder einen Blog -Beitrag oder was auch immer) mit so vielen Tags markieren können, wie Sie möchten. Später möchten Sie Anfragen ausführen, um die Lesezeichen auf eine Vereinigung oder eine Schnittstelle von Tags zu beschränken. Sie möchten auch einige Tags aus dem Suchergebnis ausschließen (z. B.: minus) ausschließen.
"MySqlicious" -Lösung
In dieser Lösung hat das Schema nur einen Tisch, es wird denormalisiert. Dieser Typ wird als „MySQLicious -Lösung“ bezeichnet, da MySQLicious del.icio.us Daten in eine Tabelle mit dieser Struktur importiert.
Intersektion (und) Abfrage für "Suche+Webservice+Semweb":
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags LIKE "%semweb%"
Union (oder) Abfrage für "Suche | WebService | Semweb":
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
OR tags LIKE "%webservice%"
OR tags LIKE "%semweb%"
Minus-Abfrage für "Suchen+WebService-Semweb"
SELECT *
FROM `delicious`
WHERE tags LIKE "%search%"
AND tags LIKE "%webservice%"
AND tags NOT LIKE "%semweb%"
"Scuttle" -Lösung
Schutt organisiert seine Daten in zwei Tabellen. Diese Tabelle "sccategories" ist das "Tag" -Table und hat einen fremden Schlüssel zum "Lesezeichen" -Table.
Kreuzung (und) Abfrage für "Lesezeichen+WebService+Semweb":
SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
HAVING COUNT( b.bId )=3
Zuerst werden alle Lesezeichen-Tag-Kombinationen durchsucht, wobei das Tag "Lesezeichen", "WebService" oder "Semweb" (C.Category in ("Lesezeichen", "WebService", "Semweb") ist, dann nur die Lesezeichen, die die Lesezeichen, die Haben Sie alle drei Tags durchsucht, nach denen berücksichtigt wird (zählt (B.bid) = 3).
Union (oder) Abfrage für „Lesezeichen | WebService | Semweb“:Lassen Sie einfach die Klausel aus und Sie haben Union:
SELECT b.*
FROM scBookmarks b, scCategories c
WHERE c.bId = b.bId
AND (c.category IN ('bookmark', 'webservice', 'semweb'))
GROUP BY b.bId
Minus (Ausschluss) Abfrage für „Lesezeichen+WebService-Semweb“, dh Lesezeichen und Webservice und nicht Semweb.
SELECT b. *
FROM scBookmarks b, scCategories c
WHERE b.bId = c.bId
AND (c.category IN ('bookmark', 'webservice'))
AND b.bId NOT
IN (SELECT b.bId FROM scBookmarks b, scCategories c WHERE b.bId = c.bId AND c.category = 'semweb')
GROUP BY b.bId
HAVING COUNT( b.bId ) =2
Auslassen, dass die Anzahl der Zähler zur Abfrage für „Lesezeichen | WebService-Semweb“ führt.
"Toxi" -Lösung
Toxi kam mit einer Drei-Tisch-Struktur. Über die Tabelle "Tagmap" sind die Lesezeichen und die Tags mit N-zu-M verwandt. Jedes Tag kann zusammen mit verschiedenen Lesezeichen verwendet werden und umgekehrt. Dieses DB-Schema wird auch von WordPress verwendet. Die Abfragen sind genau die gleichen wie in der Lösung „Scuttle“.
Kreuzung (und) Abfrage für "Lesezeichen+WebService+Semweb"
SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
HAVING COUNT( b.id )=3
Union (oder) Abfrage für "Lesezeichen | WebService | Semweb"
SELECT b.*
FROM tagmap bt, bookmark b, tag t
WHERE bt.tag_id = t.tag_id
AND (t.name IN ('bookmark', 'webservice', 'semweb'))
AND b.id = bt.bookmark_id
GROUP BY b.id
Minus (Ausschluss) Abfrage für „Lesezeichen+WebService-Semweb“, dh Lesezeichen und Webservice und nicht Semweb.
SELECT b. *
FROM bookmark b, tagmap bt, tag t
WHERE b.id = bt.bookmark_id
AND bt.tag_id = t.tag_id
AND (t.name IN ('Programming', 'Algorithms'))
AND b.id NOT IN (SELECT b.id FROM bookmark b, tagmap bt, tag t WHERE b.id = bt.bookmark_id AND bt.tag_id = t.tag_id AND t.name = 'Python')
GROUP BY b.id
HAVING COUNT( b.id ) =2
Auslassen, dass die Anzahl der Zähler zur Abfrage für „Lesezeichen | WebService-Semweb“ führt.
Andere Tipps
Mit Ihrer Drei-Tab-Lösung ist nichts auszusetzen.
Eine andere Möglichkeit besteht darin, die Anzahl der Tags zu begrenzen, die auf einen Artikel angewendet werden können (wie 5 in SO) und diese direkt zu Ihrer Artikeltabelle hinzufügen.
Die Normalisierung des DB hat seine Vorteile und Nachteile, genau wie die hart umkassenden Dinge in einem Tisch Vorteile und Nachteile.
Nichts sagt, dass man beides nicht tun kann. Es verstößt gegen relationale DB -Paradigmen, um Informationen zu wiederholen. Wenn das Ziel jedoch die Leistung ist, müssen Sie möglicherweise die Paradigmen brechen.
Ihre vorgeschlagene Implementierung von drei Tabellen funktioniert für das Markieren.
Stack Overflow verwendet jedoch unterschiedliche Implementierung. Sie speichern Tags in der Varchar -Spalte in der Post -Tabelle im Klartext und verwenden die Volltextindexierung, um Beiträge abzuholen, die den Tags entsprechen. Zum Beispiel posts.tags = "algorithm system tagging best-practices"
. Ich bin sicher, dass Jeff das irgendwo erwähnt hat, aber ich vergesse wo.
Die vorgeschlagene Lösung ist die beste-wenn nicht die einzig praktikable, die ich mir vorstellen kann, um die viel-zu-Viele-Beziehung zwischen Tags und Artikeln anzugehen. Meine Stimme ist also für "Ja, es ist immer noch das Beste." Ich würde mich jedoch für Alternativen interessieren.
Wenn Ihre Datenbank indexbare Arrays unterstützt (z. B. nach PostgreSQL), würde ich eine vollständig denormalisierte Lösung empfehlen - speichern Tags als Array von Zeichenfolgen in derselben Tabelle. Wenn nicht, ist ein sekundärer Tabellenzuordnungsobjekte zu Tags die beste Lösung. Wenn Sie zusätzliche Informationen gegen Tags speichern müssen, können Sie eine separate Tags -Tabelle verwenden. Es macht jedoch keinen Sinn, einen zweiten Join für jede Tag -Suche vorzustellen.
Ich möchte vorschlagen, mySQlicious für eine bessere Leistung optimiert zu werden. Vorher sind die Nachteile der Toxi -Lösung (3 Tabelle)
Wenn Sie Millionen von Fragen haben und jeweils 5 Tags enthält, werden in der Tagmap -Tabelle 5 Millionen Einträge enthalten. Zuerst müssen wir zuerst 10.000 Tagmap -Einträge basierend auf der Tagsuche herausfiltern und dann erneut übereinstimmende Fragen dieser 10.000 herausfiltern. Wenn Sie also herausfiltern, wenn die künstliche ID einfach numerisch ist, ist es in Ordnung, aber wenn es sich um eine Art UUID (32 VARCHAR) handelt, benötigt es einen größeren Vergleich, obwohl sie indiziert ist.
Meine Lösung:
Wenn ein neues Tag erstellt wird, haben Sie Zähler ++ (Basis 10) und konvertieren Sie diesen Gegenstand in Base64. Jetzt hat jeder Tag -Name Base64 ID. und übertragen Sie diese ID zusammen mit dem Namen an die Benutzeroberfläche. Auf diese Weise haben Sie maximal zwei Zeichen, bis wir 4095 Tags in unserem System erstellt haben. Verketten Sie nun diese Mehrfach -Tags in jede Spalte für Frage -Tabellen -Tags. Fügen Sie auch Trennzeichen hinzu und lassen Sie ihn sortiert.
Also sieht der Tisch so aus
Fragen Sie beim Abfragen auf der ID anstelle des echten Tag -Namens. Seit es ist Sortiert, and
Bedingung auf dem Tag ist effizienter (LIKE '%|a|%|c|%|f|%
).
Beachten Sie, dass der Einzelraum -Trennzeichen nicht ausreicht und wir einen doppelten Trennzeichen benötigen, um Tags wie zu unterscheiden sql
und mysql
Weil LIKE "%sql%"
wird zurückkehren mysql
Ergebnisse auch. Sollte sein LIKE "%|sql|%"
Ich weiß, dass die Suche nicht indiziert ist, aber Sie haben möglicherweise immer noch in anderen Spalten im Zusammenhang mit dem Artikel wie Author/DateTime an anderer Hinweise zum vollständigen Tabellen -Scan führen.
Mit dieser Lösung müssen schließlich kein innerer Join erforderlich sein, wenn Millionen Datensätze mit 5 Millionen Rekorde unter Join -Bedingung verglichen werden müssen.
CREATE TABLE Tags (
tag VARHAR(...) NOT NULL,
bid INT ... NOT NULL,
PRIMARY KEY(tag, bid),
INDEX(bid, tag)
)
Anmerkungen:
- Dies ist besser als Toxi, da es nicht extra viele durchläuft: viele Tabellen, die die Optimierung erschweren.
- Sicher, mein Ansatz ist aufgrund der redundanten Tags möglicherweise etwas sperriger (als Toxi), aber das ist ein kleiner Prozentsatz der ganz Datenbank und die Leistungsverbesserungen können signifikant sein.
- Es ist sehr skalierbar.
- Es gibt keinen Ersatz (weil es nicht braucht)
AUTO_INCREMENT
Pk. Daher ist es besser als Scuttle. - Mysqlicious ist scheiße, weil es keinen Index verwenden kann (
LIKE
mit führend Wildkarte; falsche Hits auf Substrings) - Verwenden Sie für MySQL unbedingt Engine = InnoDB, um "Clustering" -Effekte zu erhalten.
Verwandte Diskussionen (für MySQL):
Viele: Viele Mapping -Tabellenoptimierung
Bestellte Listen