2013-05-18 17 views
6

Używanie schematu bazy danych do tagowania z tego zaakceptowanego pytania answer Czy możliwe jest zapytanie za pomocą group_concat, które działa z dużą ilością danych? Muszę uzyskać elementy z ich tagami dla wszystkich elementów oznaczonych tagiem x. Używanie zapytania z group_concat o ~ 5 milionów tagów jest bardzo powolne w ciągu> 15 sekund. Bez group_concat (elementy bez tagów) wynosi ~ 0,05 sekundy.Zapytanie dotyczące tagowania za pomocą group_concat

Jako pytanie uboczne, w jaki sposób SO rozwiązuje ten problem?

+1

możesz podać przykładowe rekordy. –

+0

SO wydaje się rozwiązywać ten problem, ograniczając pytania do maksymalnie 5 tagów. I co sprawia, że ​​myślisz, że w ogóle używa 'GROUP_CONCAT()' podczas obsługi tagów? – Barmar

+1

@Barmar: limit znaczników na SO nie jest związany z wydajnością, ale raczej [w celu skupienia się na pytaniach] (http://meta.stackexchange.com/a/34743); co do [schematu SO] (http://meta.stackexchange.com/a/2678), tagi są powiązane z postami zarówno w znormalizowany sposób (tabela "PostTags"), jak i mody denormalizowanej ("Posts.Tags"). 'pole) - ta ostatnia sprawia, że ​​bardzo szybko można pobrać znaczniki postu za pomocą samego wpisu, podczas gdy pierwszy ułatwia wyszukiwanie wpisów z konkretnymi kombinacjami znaczników. – eggyal

Odpowiedz

5

Jest to prawdopodobnie przypadek złej strategii indeksowania. Adaptacja schematu pokazanego na the accepted answer kwestii, do którego połączony:

CREATE Table Items (
    Item_ID SERIAL, 
    Item_Title VARCHAR(255), 
    Content TEXT 
) ENGINE=InnoDB; 

CREATE TABLE Tags (
    Tag_ID  SERIAL, 
    Tag_Title VARCHAR(255) 
) ENGINE=InnoDB; 

CREATE TABLE Items_Tags (
    Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID), 
    Tag_ID  BIGINT UNSIGNED REFERENCES Tags (Tag_ID), 
    PRIMARY KEY (Item_ID, Tag_ID) 
) ENGINE=InnoDB; 

zauważyć, że:

  • MySQL typ SERIAL danych jest aliasem dla BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE i, jako taki, jest indeksowana;

  • Definiowanie ograniczeń klucza obcego w Items_Tags tworzy indeksy w kolumnach klucza obcego.

+0

Hmm Jestem dość pewien, że mam te same indeksy, sprawdzę później wieczorem. –

+0

@amiawizard: Jakieś wieści? – eggyal

3

chciałbym zaproponować, aby mieć hybrydę pomiędzy normalized danych i denormalized danych.
Więc za pomocą znormalizowanej struktury dostarczone przez eggyal chciałbym zrobić następującą strukturę: nieznormalizowana

CREATE TABLE Items_Tags_Denormalized (
    Item_ID BIGINT UNSIGNED REFERENCES Items (Item_ID), 
    Tags  BLOB, 
    PRIMARY KEY (Item_ID) 
) ENGINE=InnoDB; 

W kolumnie Tags byś mieć wszystkie znaczniki (Tag_Title) do odpowiedniego Item_ID.
Teraz masz 2 sposoby osiągnięcia tego celu:

  • stworzenia crona, który działa okresowo, która zbuduje tę tabelę Items_Tags_Denormalized użyciu GROUP_CONCAT lub cokolwiek (garnitury zaleta: nie umieścić dodatkowe obciążenie podczas wstawiania lub usuwania w Items_Tags tabeli; Wada: nieznormalizowane tabeli nie zawsze będzie na bieżąco (w zależności od tego, jak często można uruchomić crona))

  • tworzyć triggers dla Items_Tags tabeli na wkładce i usuwać w celu utrzymania aż do dnia dzisiejszego Items_Tags_Denormalized tabela (zaleta: denormalizowane t mogli zawsze być na bieżąco; wadę: dodatkowe obciążenie podczas wstawiania lub usuwania w Items_Tags tabeli)

Wybierz cokolwiek rozwiązanie najlepiej odpowiada Twoim potrzebom rozważa zalety i wady.

Tak więc na końcu pojawi się tabela Items_Tags_Denormalized, z której będzie można odczytać tylko bez wykonywania dodatkowych operacji.

+0

Dlaczego nie dodać zdenormalizowanego pola 'Tagi' do tabeli" Items "(jak to robi SO)? – eggyal

+0

ważne jest, aby mieć oddzielne modele: jeden znormalizowany i jeden denormalizowany, twoje rozwiązanie jest ok, ale z pov projektu sugerowałbym zachować go osobno z różnych powodów: musisz przebudować tabelę, musisz dodać więcej kolumn itp. Plus jeśli dodasz kolumny znaczników denormalizowanych w elementach, których wydajność spadnie: większy rozmiar = wolniejsze zapytania – Stephan

1

Dlaczego użyłbyś do tego group_concat? Dla danego tagu x powiedziałeś, że wybór listy przedmiotów jest szybki. Dla danej listy przedmiotów uzyskanie wszystkich tagów powinno być również szybkie. I czy zazwyczaj nie ma jakiegoś rodzaju ograniczenia, mam na myśli, że normalne strony internetowe nie wyświetlają 100000 wpisów na jednej stronie.

Proponuję:

drop temporary table if exists lookup_item; 

create temporary table lookup_item (item_id serial, primary key(item_id)); 

insert into lookup_item select i.id as item_id 
from items i 
where exists (select * from items_tags where item_id = i.id and tag_id = <tag_id>) 
and <other conditions or limits>; 

select * from lookup_item 
inner join items_tags it on it.item_id = i.id 
inner join tags t on t.id = it.tag_id 
order by i.<priority>, t.<priority> 

priorytet może być modyfikowany na ostatniej pozycji i pewnego rodzaju znaczenie dla tagów.

Następnie otrzymasz każdy przedmiot z jego tagami. Jedyną pracą w kodzie jest sprawdzenie, czy linia wynikowa ma następny element.

1

Jeśli dobrze rozumiem, GROUP_CONCAT nie jest jedyną rzeczą, którą usuwasz, dzięki czemu zapytanie jest szybsze bez tagów. Wewnątrz GROUP_CONCAT wybierasz Tags.Tag_Title i wymuszasz dostęp do tabeli Znaczników.

Możesz spróbować uruchomić GROUP_CONCAT z Items_Tags.Tag_ID, aby przetestować moją teorię.

Powiązane problemy