ugrás a tartalomhoz

Mysql lekérdezés optimalizálása

plip · 2013. Szep. 20. (P), 22.04
Sziasztok!
Egy viszonylag egyszerű ügyben kérném a véleményeteket/segítségeteket:
Adott egy adatbázis, egyszerűség kedvéért 2 tábla:
A: id, b_id
B: id, name
id természetesen primary key, b_id pedig a b.id-vel van kapcsolatban.

Adott a query:

SELECT  COUNT(A.id) db, B.name 
	FROM  A 
		INNER JOIN B ON A.B_id=B.id          
	GROUP BY B.id                        
	ORDER BY db DESC      
	LIMIT 0, 10;
Tehát a B elemeiből hány szerepel A-ban és eszerint egy ranglista. Ez szépen működött is, amíg meg nem hízott az adatbázis. Jelenleg A-ban 220.000, B-ben 8000 rekord van. A lekérdezés átlag 4-7 mp alatt fut le.
Hogyan lehetne gyorsítani?
- myisam/innodb? Jelenleg myisam, próbáltam innodb-vel is, még lassabbnak tűnt.
- Van-e jelentősége, hogy B_id foreign key, vagy csak simán key?
- vagy pedig jöhet a denormalizálás, és vegyek fel B-re egy számláló mezőt, amit triggerrel/php-val updatelek?


A táblaszerkezetek:

CREATE TABLE A (
  id bigint(11) unsigned NOT NULL AUTO_INCREMENT,
  B_id int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (id),
  KEY k_B_id (B_id),
) ENGINE=MyISAM



CREATE TABLE B (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  name varchar(120) CHARACTER SET utf8 COLLATE utf8_hungarian_ci NOT NULL,
  PRIMARY KEY (id),
) ENGINE=MyISAM
Egyébként a helyzet még bonyolódik egy C táblával is, ahol B-ben van egy C_id oszlop hasonló kapcsolattal, és ezekből kell különböző statisztikákat lekérdezni, de már a 2táblás verziónál gondok vannak.

Köszönöm a segítséget!
 
1

Query

janoszen · 2013. Szep. 22. (V), 11.44
Az EXPLAIN parancs mit mond erre? Egyebkent teljesen jonak nez ki, bar en a MyISAM-ot InnoDB-re cserelnem, tekintettel arra, hogy a default cuccokat a MySQL-ben mindig jobban tesztelik.

Ezen felul erdemes lenne megnezni a szerver metrikait peldaul a mysqltuner.pl-el, biztos vagyok benne, hogy 1-2 cache ertek tul alacsonyra van tekerve.
2

explain

plip · 2013. Szep. 23. (H), 12.01
SZia!
Az explain ezt mondja:

+----+-------------+-------+--------+---------------+---------+---------+--------------------+--------+---------------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                | rows   | Extra                           |
+----+-------------+-------+--------+---------------+---------+---------+--------------------+--------+---------------------------------+
|  1 | SIMPLE      | A     | ALL    | B_id          | NULL    | NULL    | NULL               | 222739 | Using temporary; Using filesort |
|  1 | SIMPLE      | B     | eq_ref | PRIMARY       | PRIMARY | 4       | A.B_id             |      1 | Using where                     |
+----+-------------+-------+--------+---------------+---------+---------+--------------------+--------+---------------------------------+
A mysqltuner pedig ezt:
-------- Performance Metrics -------------------------------------------------
[--] Up for: 5m 57s (8K q [23.227 qps], 1K conn, TX: 1M, RX: 632K)
[--] Reads / Writes: 73% / 27%
[--] Total buffers: 106.0M global + 2.7M per thread (151 max threads)
[OK] Maximum possible memory usage: 511.8M (25% of installed RAM)
[OK] Slow queries: 0% (2/8K)
[OK] Highest usage of available connections: 3% (6/151)
[OK] Key buffer size / total MyISAM indexes: 16.0M/152.8M
[OK] Key buffer hit rate: 99.1% (918K cached / 8K reads)
[!!] Query cache efficiency: 19.8% (733 cached / 3K selects)
[OK] Query cache prunes per day: 0
[OK] Sorts requiring temporary tables: 2% (14 temp sorts / 497 sorts)
[OK] Temporary tables created on disk: 13% (53 on disk / 399 total)
[OK] Thread cache hit rate: 99% (6 created / 1K connections)
[!!] Table cache hit rate: 2% (128 open / 4K opened)
[OK] Open file limit used: 17% (183/1K)
[OK] Table locks acquired immediately: 100% (4K immediate / 4K locks)
[!!] InnoDB data size / buffer pool: 40.5M/8.0M

-------- Recommendations -----------------------------------------------------
General recommendations:
    Run OPTIMIZE TABLE to defragment tables for better performance
    MySQL started within last 24 hours - recommendations may be inaccurate
    Increase table_cache gradually to avoid file descriptor limits
Variables to adjust:
    query_cache_limit (> 1M, or use smaller result sets)
    table_cache (> 128)
    innodb_buffer_pool_size (>= 40M)
Egy két értéket feltekertem, de nem lett gyorsabb, optimize is megvolt, később megpróbálom innodb-vel is
3

Using temporary; Using

Hidvégi Gábor · 2013. Szep. 23. (H), 12.40
Using temporary; Using filesort
Emiatt lassú, valami nincs rendben az indexekkel, szerintem azért, mert a lekérdezésben ez van: ORDER BY db DESC.
5

Jaigen

janoszen · 2013. Szep. 23. (H), 12.46
Jaigen, MySQL-ben nincsenek fuggveny-alapu indexek, igy a COUNT() nem lesz indexelheto. Kerulo megoldaskent lehet azt csinalni, hogy idonkent megszamolod az egeszet es letarolod.
6

igen

plip · 2013. Szep. 23. (H), 12.52
Igen, valami ilyesmire tippeltem, akkor marad a harmadik verzió, azaz
"- vagy pedig jöhet a denormalizálás, és vegyek fel B-re egy számláló mezőt, amit triggerrel/php-val updatelek?"
Trigger, vagy php?
4

ORDER BY

Poetro · 2013. Szep. 23. (H), 12.44
Nem vagyok egy DBA, de szerintem a számított értéken történő rendezés miatt lesz lassú, ugyanis arra nincs sem index se semmi. Azaz csinálnia kell egy teljes lekérdezést, amire groupol, majd az alapján rendez, és mivel egy nem valós mezőre rendezel ez elég költséges lesz.
7

Pontosan

Pepita · 2013. Szep. 24. (K), 01.37
Nincs rá index - letérdel.
Nekem viszont igencsak hibázik az "AS" is:
SELECT  COUNT(A.id) db,...
HELYETT:
SELECT  COUNT(A.id) AS 'db',
Ha ez nem dob hibát, kérlek valaki homályosítson fel, miért elhagyható? (Biztos szintaktika.)

Megoldási javaslat:
- Gondolom az A táblába egy tevékenységként egy rekordot szúrsz be (B.id-vel együtt). Itt azért jó lenne tudni a konkrét feladatot is, legalábbis nekem.
- Ha így van, csinálsz szépen egy C táblát a statisztikának, és azt update-eled, mikor A-ba beszúrsz (utána), így ezt a statisztikát egy (vagy B és C) táblából kéred le, amit persze helyesen indexelsz is (és kiegészítheted a neked tetsző dolgokkal).
- Természetesen itt már kötelező lesz az InnoDB, hogy ezt tranzakcióban tedd meg, ne lehessen benne "elírás".
- Ha a rangsorban B-ből nagyon kevés adatra van szükség (pl. csak a name), akkor megteheted azt a csúnyácska megoldást is, hogy amikor a C-be beszúrni kell (mert B-ből új id került A-ba), akkor beszúrod ezt az adatot is. Így egytáblás, indexelt lekérdezésed lesz = szupergyors, ha nem is szép megoldás (mert adatot nem duplikálunk).
- A lényeg, hogy amikor adatot viszel be, akkor frissíts mindent, de egy tranzakcióban, hogy semmi ne akadjon össze, és automatizálva legyen.
- Azért is javaslom az InnoDB-t, mert megadhatsz külső kulcsokat is, amik alapján pl. a B táblából való törléskor törlődnek az A és C tábla rá hivatkozó rekordjai is, vagy nem engedi törölni, ha van rá hivatkozás. Hasznos dolog ám ez, így nem kell PHP-ban ügyeskedni, hogy ki ne felejts valamit. (Azért hogy ellene is szóljak kicsit az InnoDB-nek: már nem emlékszem, de vagy INSERT és UPDATE, vagy SELECT műveletre kicsit lassabb, mint a MyIsam. De cserébe ott a tranzakció, és a többi előny.)

Szerintem az adatbázis nagyon okos felépítése is fontos, hogy a későbbi (változó) lekérdezésekhez is jó legyen. Itt bizony előre kell optimalizálni (most Gábornál biztos kivertem a biztit :)).
8

Az "AS" nem kötelező.

Hidvégi Gábor · 2013. Szep. 24. (K), 07.43
Az "AS" nem kötelező.

mert adatot nem duplikálunk
Vannak helyzetek, amikor célszerű adatokat duplikálni, mert anélkül olyan lekérdezéseket kéne írni, amelyek vagy nagyon bonyolultak vagy lassúak lesznek.

Vannak helyzetek, amikor kötelező az adatok duplikációja, mint például ha elektronikusan számlát állítasz ki.
12

Szerintem az nem jó,

Pepita · 2013. Szep. 25. (Sze), 10.00
hogy "AS" nem kötelező. Lehet, hogy BDE-s beidegződés, de én ki nem hagyom, már csak az átláthatóság miatt sem.

Az adatduplikálás akkor sem "szép", ha muszáj. De ez van, néha igen hasznos (vagy mint írod: kötelező), de elméleti szempontból helytelen. Viszont a gyakorlat nem csak elméletekből áll... De amikor csak lehet, kerüljük a duplikálást.
Amit idéztél, egy általam jónak tartott adatbáziskezelés-tanártól hallottam, és ahol lehet, be is tartom.
14

Az adatduplikálás akkor sem

Joó Ádám · 2013. Szep. 26. (Cs), 00.34
Az adatduplikálás akkor sem "szép", ha muszáj. De ez van, néha igen hasznos (vagy mint írod: kötelező), de elméleti szempontból helytelen. Viszont a gyakorlat nem csak elméletekből áll... De amikor csak lehet, kerüljük a duplikálást.


Ha a duplikálástól gyorsabb lesz az alkalmazásod, akkor by all means, duplikálj. Ezt hívják cache-nek.
15

Melyik elmélet? Nem mindegy.

pp · 2013. Szep. 26. (Cs), 06.06
"Elméleti szempontból helytelen"

Úgy általában elméleti szempontból nem helytelen (ami nem azt jelenti, hogy ne lenne elmélet, ami szerint helytelen).

Adatbázis normalizálás szempontjából nevezhetjük annak. Vagy nézhetnénk a konzisztencia szempontjából, mert úgy meg nem előnyös, mert nehéz lesz azt fenntartani (már a konzisztenciát).

Ha sebesség szempontjából nézzük, akkor elméletileg nem helytelen, ha adattárolás biztonsága felől közelítjük a kérdést, akkor pedig elméletileg is elvárt az adatduplikálás.

Szóval a skála elég széles.
16

Igen, mindkettőtöknek igaza van

Pepita · 2013. Szep. 28. (Szo), 13.41
Köszönöm a pontosítást.

Nekem alapmegközelítésem - adatbázis tervezéskor -, hogy lehetőleg ne kelljen duplikálni. Viszont - épp azért, mert a skála elég széles -, a legtöbb esetben ettől el kell térni, akár "csak" gyorsaság szempontjából is (pl. fórumtéma, kommentek: felh. név).
Talán afelé lehet egy igazság, hogy minél kevesebbet duplikáljunk, és főként: soha ne feleslegesen (mondjuk az már szarvashiba).
Ezért (egyszerűség okán) használom a "csúnya" kifejezést rá, ami persze nem a legmegfelelőbb jelző...

Nekem helyi (BDE, MSAccess) kezelőkön indult a tanulásom, így egy kicsit más szemlélet is "bentmaradt": ezeknél, ha csak elfér fizikai RAM-ban, akkor ott is van a teljes adatbázis, indexekkel együtt. Néha (változások után, külön szálként) ráment a fizikai állományra. Ilyen szituban nemigazán jelent sebességkülönbséget a duplikáció, viszont nagyobb lesz a DB, hamarabb kerül ki a fizikai RAM-ból. Mindez persze kb. 100 000-es rekordszámig volt igaz (ma nem tudom, nemigen használom már).

A MySql-től Pdo, stb.. kezelők egész mások, nyilván nagyobb rugalmasságot követelnek meg, és ritka eset az, hogy "ott pihennek" a RAM-ban, hacsak nem külön "kérjük" erre. Ezeket a különbségeket folyamatosan figyelembe kell vegyem - ez igaz.
9

tranzakció

plip · 2013. Szep. 25. (Sze), 01.26
Tranzakció nem játszik, mivel a statisztika számítás hibája miatt nem maradhat el adatrögzítés. Továbbá nincs törlés sem.

Továbbá bonyolódik a helyzet azzal, hogy rész-statisztikákat is kell számolni, vagyis nem a teljes 'A' táblából, hanem csak egy részéből (havi statisztika). Tehát a külön statisztika rögzítése akár külön táblában, akár B tábla külön oszlopában, nem jöhet szóba, mint megoldás.
10

PG

Hidvégi Gábor · 2013. Szep. 25. (Sze), 06.54
Akkor lehet érdemes lenne a statisztika tábláit áttenni PostgreSQL-be. Viszont az általad leírtak alapján nem teljesen világos, miért nem jöhet szóba a külön táblás rögzítés: mindig a legfrissebb statisztikai adatokra van szükség, pár perces vagy órás lemaradás számít?
11

Tranzakció

Pepita · 2013. Szep. 25. (Sze), 09.54
Ha nem akarsz egy DB-szeméttombot, mindig tranzakcióban szúrj be, update-elj!
Bármikor előfordulhat egyidejűség egy másik szállal.
Ezen kívül ott a lehetőség, hogy ha sikertelen a tranzakció, naplózod, hogy miért (hol akadt el), sleepels 1-2 s-ot, és újra próbálod. Ezt 3x simán megteheted. Egyébként meg kiírod a Júzernek, hogy próbálja kicsit később...

A C táblába csak azokat az adatokat viszed át, amik a statisztika készítéséhez szükségesek, tehát a dátumokat is. Simán megoldod lekérdezésen belül az időintervallumot is. Tehát A tábla minden változásakor aszerint frissíted a C-t.

Ezen a ponton már tényleg a konkrét feladatot kéne ismerni, a mintatáblák - gondolom - elfednek egy csomó fontos infót a szerkezetről-működésről. Így nehéz lesz többet segíteni.

Mi az, hogy "nincs törlés sem"?
Ha ez azt jelenti, hogy A táblából sosem törölsz, akkor még könnyebb a dolgod C-vel.
Tehát a külön statisztika rögzítése akár külön táblában, akár B tábla külön oszlopában, nem jöhet szóba, mint megoldás.
Szerintem meg épphogy ez lesz a helyes megoldás, még mindig, pedig nincs "igazi" adattáblánk, amivel gondolkodjunk.
13

Használj count(*)-ot, MyISAM

tgr · 2013. Szep. 25. (Sze), 23.07
  • Használj count(*)-ot, MyISAM tábláknál jelentősen gyorsabb lehet.
  • Használj indexet. Mivel a B_id alapján csoportosítasz, ami indexelve van, nincs szükség full table sortra. Ha mégis megtörténik, akkor az optimizer úgy gondolta, hogy kézzel rendezni a táblát gyorsabb, mint az indexet végigjárni - általában neki van igaza, de egyszer-egyszer mégse, egy próbát megér:
    
    SELECT  COUNT(A.id) db, B.name 
    	FROM A  FORCE INDEX FOR GROUP BY (B_id)
    		INNER JOIN B ON A.B_id=B.id
    	GROUP BY B.id                        
    	ORDER BY db DESC      
    	LIMIT 0, 10;
    
  • Használj subquery-t:
    
    SELECT  name,
            (
                    SELECT COUNT(*)
                    FROM   A
                    WHERE A.B_id = B.id
            ) db
    	FROM  B
    	GROUP BY id                        
    	ORDER BY db DESC      
    	LIMIT 0, 10;
    
  • Használj InnoDB táblákat és triggereket. (Ez a lehetőség mazochistáknak különösen ajánlott - a MySQL tárolt eljárások nyelvének kellemesen 80-as évekbeli feelingje van.)
  • Ha nem fontos az eredmények valós idejű frissítése, akkor az előbbihez hasonló eredményt érhetsz el sokkal kevesebb szívással, ha teszel A-ra egy processed flaget, és cronból percenként végigmész a processed=0 rekordokon, a számukat hozzáadod B-ben a megfelelő sor count mezőjéhez, és 1-re állítod a flaget. Ha biztosítod, hogy egyszerre csak egy cron fusson, még InnoDB-re sem feltétlenül kell átváltanod miatta.
  • Ha fontos az eredmények valós idejű frissítése, akkor is használhatod az előző megoldást, csak a tárolt számlálóhoz hozzá kell összegezni a processed=0 sorok számát (egy okos indexszel ez gyors művelet).