Optymalizacja bazy danych MySQL

0

Opis:

Do aplikacji będą logować się użytkownicy różnych sklepów, które mają różne produkty, parametry i właściwości parametrów. Uznałem, że mogą występować konflikty identyfikatorów w każdej z tabel, więc jako osobny PRIMARY KEY dodałem identyfikator... w sumie wiersza (czy dobrze?).

Użytkownik jest przypisany do danego sklepu, który ma swoje produkty. Każdy z produktów ma parametry, a one swoje właściwości.
Sklep ma dużo produktów, a produkt jeszcze więcej parametrów. A każdy z parametrów posiada właściwości, też może mieć kilka (od 0 do x). Dzięki tym informacjom doszedłem do takiej bazy danych.

Struktura tabel wygenerowana przy użyciu SHOW CREATE TABLE name:

-- users --
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created_at` datetime DEFAULT current_timestamp(),
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

-- shops --
CREATE TABLE `shops` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL,
  `name` varchar(128) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  CONSTRAINT `shops_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `users` (`id`),
  CONSTRAINT `shops_ibfk_2` FOREIGN KEY (`id`) REFERENCES `products` (`shop_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- products ---
CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `product_name` varchar(512) CHARACTER SET utf8 NOT NULL,
  PRIMARY KEY (`id`),
  KEY `shop_id` (`shop_id`),
  KEY `product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- parameters --
CREATE TABLE `parameters` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `parameter_id` int(11) NOT NULL,
  `parameter_name` varchar(128) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `shop_id` (`shop_id`),
  KEY `product_id` (`product_id`),
  KEY `parameter_id` (`parameter_id`),
  CONSTRAINT `parameters_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- parameter_values --
CREATE TABLE `parameter_values` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `shop_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `parameter_id` int(11) NOT NULL,
  `value_id` int(11) NOT NULL,
  `value_name` varchar(128) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `shop_id` (`shop_id`),
  KEY `parameter_id` (`parameter_id`),
  KEY `product_id` (`product_id`),
  KEY `value_id` (`value_id`),
  CONSTRAINT `parameter_values_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `products` (`product_id`),
  CONSTRAINT `parameter_values_ibfk_2` FOREIGN KEY (`parameter_id`) REFERENCES `parameters` (`parameter_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Ten wątek ma na celu poprawę mojej bazy danych pod kątem wydajności i optymalizacji. Może ktoś bardziej doświadczony udzieli rad, wskazówek, sugestii - co można zrobić lepiej, co jest źle, a co dobrze, aby uzyskać jak najlepsze efekty.

Czy to jest dobre podjeście?

3
inven napisał(a):

Ten wątek ma na celu poprawę mojej bazy danych pod kątem wydajności i optymalizacji. Może ktoś bardziej doświadczony udzieli rad, wskazówek, sugestii - co można zrobić lepiej, co jest źle, a co dobrze, aby uzyskać jak najlepsze efekty.

Ale jakie efekty chcesz uzyskać? Masz jakiś problem wydajnościowy z tą bazą czy pytasz po prostu o weryfikację poprawności modelu danych na przykład żeby był zgodny z 3 postacią normalną?

Na Twoje pytanie nie da się jednoznacznie odpowiedzieć bo wszystko zależy co chcesz osiągnąć? Spójność danych, szybki odczyt, jakie zapytania będziesz robił na bazie, itd.

W zależności od problemu może się okazać, na przykład, że rozwiązaniem nie będzie model relacyjny tylko kolumnowy.

0

Najbardziej zależy mi na wydajności. Zapytania, jakie wykonuję to codzienna aktualizacja danych i pobieranie konkretnych informacji, tj. przykładowo produkty dla danego sklepu, które zawierają określone parametry.

SELECT DISTINCT A.product_id, A.product_name, B.parameter_name, B.parameter_id, GROUP_CONCAT(CONCAT(C.value_id, ';', C.value_name) SEPARATOR ', ') AS parameters
FROM parameter_values as C
LEFT JOIN parameters as B ON C.product_id = B.product_id AND B.parameter_id = C.parameter_id
LEFT JOIN products as A ON A.product_id = B.product_id
WHERE A.shop_id = " . $shopId . " AND B.parameter_id IN(" . implode(',', $ids) . ")
GROUP BY A.product_id, B.parameter_id

Obecnie rekordów w tabelach jest około 50.000 (products), parametrów 300.000 (parameters), wartości parametrów 700.000 (parameter_values). I takie zapytanie trwa około 10s.

markone_dev napisał(a):

Ale jakie efekty chcesz uzyskać? Masz jakiś problem wydajnościowy z tą bazą czy pytasz po prostu o weryfikację poprawności modelu danych na przykład żeby był zgodny z 3 postacią normalną?

Tak, chciałbym uzyskać również weryfikację modelu.

2

@inven:

Chcesz powiedzieć, ze to jest coś z grupy wdrożeń multitenant ?

Wiedza branżowa zna ze 3 główne implementacje. Każda ma jakieś tam skutki.... zajętość baz, uprawnienia i ich kontrola, łatwość programowania, backup itd...
Zamieszczam artykuł z kręgu Javy, bo tam spotkałem głębszą analizę możliwych wzorców jak to robić

https://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_multitenant.htm

0
inven napisał(a):

Obecnie rekordów w tabelach jest około 50.000 (products), parametrów 300.000 (parameters), wartości parametrów 700.000 (parameter_values). I takie zapytanie trwa około 10s.

A ile sklepów masz? Niestety nie jestem ekspertem od MySQL, więc na temat optymalizacji zapytań na tym silniku się nie wypowiem, ale może warto rozważyć podejście w którym każdy tenant (klient) ma swoją bazę danych? Już nie chodzi tylko o problem optymalizacji, ale również bezpieczeństwa.

Edit: @ZrobieDobrze już wrzucił przykładowy art. Ogólnie poczytaj o multi-tenant database design

2

Obecnie rekordów w tabelach jest około 50.000 (products), parametrów 300.000 (parameters), wartości parametrów 700.000 (parameter_values). I takie zapytanie trwa około 10s

  1. Dlaczego trwa 10 sekund?
  2. Ile czasu powinno trwać?
0
yarel napisał(a):

Obecnie rekordów w tabelach jest około 50.000 (products), parametrów 300.000 (parameters), wartości parametrów 700.000 (parameter_values). I takie zapytanie trwa około 10s

  1. Dlaczego trwa 10 sekund?
  2. Ile czasu powinno trwać?

I gdzie jest plan zapytania? No by chyba jest coś takiego dla MySQLa?

1

Po co ten DISTINCT na początku? To ci może zabijać wydajność
Masz indeksy na kluczach?

1

Jeszcze jedno:

i, tj. przykładowo produkty dla danego sklepu, które zawierają określone parametry.

SELECT DISTINCT A.product_id, A.product_name, B.parameter_name, B.parameter_id, GROUP_CONCAT(CONCAT(C.value_id, ';', C.value_name) SEPARATOR ', ') AS parameters
FROM parameter_values as C
LEFT JOIN parameters as B ON C.product_id = B.product_id AND B.parameter_id = C.parameter_id
LEFT JOIN products as A ON A.product_id = B.product_id
WHERE A.shop_id = " . $shopId . " AND B.parameter_id IN(" . implode(',', $ids) . ")
GROUP BY A.product_id, B.parameter_id

Obecnie rekordów w tabelach jest około 50.000 (products), parametrów 300.000 (parameters), wartości parametrów 700.000 (parameter_values). I takie zapytanie trwa około 10s.

Skoro to "produkty dla danego sklepu", do dlaczego w zapytaniu nie wychodzisz od danego sklepu? Tj. SHOPS s join PRODUCT p on p.shop_id=s.id and s.id=$shopId

Przy takiej ilości produktów baza powinna zrobić HASH JONA, jak będziesz miał nested loopa, to koszt będzie rzędu: 700 000 x 300 000 x 50 000 (+ filtrowanie rekordów i sortowanie)

Ile produktów będziesz miał per sklep? 50000 czy 1000 ?
Ile parametrów per produkt?

1

to zapytanie jest w ogóle dziwne. Pomijając kwestię o którą pyta @yarel - czemu "główną" tabelą w zapytaniu nie jest products to chyba wiesz, że left join i tak tu nie działa bo potem masz warunki w where, które robią z tego join

3
inven napisał(a):

Opis:

Do aplikacji będą logować się użytkownicy różnych sklepów, które mają różne produkty, parametry i właściwości parametrów. Uznałem, że mogą występować konflikty identyfikatorów w każdej z tabel, więc jako osobny PRIMARY KEY dodałem identyfikator... w sumie wiersza (czy dobrze?).

Użytkownik jest przypisany do danego sklepu, który ma swoje produkty. Każdy z produktów ma parametry, a one swoje właściwości.
Sklep ma dużo produktów, a produkt jeszcze więcej parametrów. A każdy z parametrów posiada właściwości, też może mieć kilka (od 0 do x). Dzięki tym informacjom doszedłem do takiej bazy danych.

Czy to jest dobre podjeście?

Czy piszesz drugie Allegro?
Czy musisz robić (na potrzeby aplikacji, nie dla siebie) analizę "pomiędzy różnymi sklepami"? Jeśli nie, to prosta reguła - "jeden sklep - jedna baza". Z wielu względów np. bezpieczeństwa danych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1