V nesčetném množství MySQL tutoriálů a manuálů je uvedeno jako příklad náhodného výběru řádku následující:
SELECT * FROM tbl ORDER BY RAND() LIMIT 1
S malými tabulkami žádný problém, ale podívejme se na EXPLAIN takového dotazu:
mysql: EXPLAIN select * from geoip order by rand() limit 1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: tbl
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 146219432
Extra: Using temporary; Using filesort
Bez ohledu na existenci jakýchkoli klíčů v tabulce tbl, výsledek je vždy ten samý: MySQL setřídí celou tabulku dle náhodné hodnoty vygenerované pro každý řádek, a pak z nich vybere jeden nejmenší. Zvlášť alarmující pro nás jsou poslední dva údaje z explain, tedy rows: 146219432 (zde vidíme, že MySQL musí analyzovat veškeré řádky tabulky) a Extra: Using temporary; Using filesort (vytváření dočasných souborů na disku je to nejhorší co vás může u mysql kdy potkat). Výběr jednoho náhodného řádku je tak přímo úměrný počtu záznamů v tabulce, a může trvat i minuty. Katastrofa.
Jak vybrat z tabulky náhodný řádek lépe a hlavně rychleji?
Obávám se, že úplně univerzální řešení neznám.
Ale za určitých počátečních předpokladů je optimalizace velmi účinná.
Požadavky na tabulku tbl:
- obsahuje unikátní auto increment ID identifikátor jako PRIMARY KEY
- z tabulky se příliš nemaže, tzn ID identifikátory jdou za sebou a nemají mezi sebou moc „děr“
- maximální ID identifikátor je přibližně roven počtu řádků tabulky
Princip optimalizace je celkem jednoduchý. Zjistit (přibližný) počet řádků v tabulce, vynásobit ho náhodnou hodnoutou mezi 0 a 1, tím získat přibližné ID náhodného řádku, a tento pak jednoduše vybrat podle primárního klíče.
SELECT @r:=RAND();
SELECT @i:=
(SELECT MIN(id) FROM tbl WHERE id>=
(SELECT (@r*(SELECT MAX(id) FROM tbl))));
SELECT * FROM tbl WHERE id=@i;
Takto formulovaný dotaz provede výběr náhodného řádku v rychlosti, nezávisle na počtu řádků v tabulce tbl. Pokud jsou v tabulce tbl často mazány řádky, nebude náhodný výběr úplně náhodný, pravděpodobnost výběru řádku těsně před „dírou“ v posloupnosti ID vlivem smazaných řádků bude právě tolikrát vyšší, kolik smazaných řádků za ním následuje. V praktickém životě je to ale obvykle fuk.
A ještě výstup explain (zkráceně) pro zvídavé:
mysql: explain SELECT @r:=RAND()\G
*************************** 1. row ***************************
::
Extra: No tables used
mysql: SELECT @i:= (SELECT MIN(id) FROM tbl WHERE id>= (SELECT (@r*(SELECT MAX(id) FROM tbl))))\G
*************************** 1. row ***************************
::
Extra: No tables used
*************************** 2. row ***************************
::
select_type: UNCACHEABLE SUBQUERY
Extra: Select tables optimized away
*************************** 3. row ***************************
::
select_type: SUBQUERY
Extra: Select tables optimized away
mysql: explain SELECT * FROM tbl WHERE id=@i\G
*************************** 1. row ***************************
::
type: const
possible_keys: PRIMARY
key: PRIMARY
ref: const
rows: 1
Extra:
Pro úplnost ještě jedna možnost, jak jde náhodný řádek z tabulky vybrat, a to díky prepared statements (v MySQL někdy od verze 4.1). Ovšem nemám praktické ani teoretické zkušenosti s performance takového dotazu, a pomocí explain se to moc analyzovat nedá:
SET @randrow:=FLOOR((SELECT COUNT(*) FROM tbl)*RAND());
PREPARE STMT FROM 'SELECT * FROM tbl LIMIT ?, 1';
EXECUTE STMT USING @randrow;
Nemám po ruce MySQL, už dlouho jsem ji ani nepoužil, takže nevím, jestli můj nápad není stejně mizerný jako ten ORDER BY RAND, ale zkusil bych
SELECT * FROM tabulka LIMIT (SELECT (COUNT(*) FROM tabulka)*RAND()),1 - prostě nic netřiď, náhodn vyber jeden řádek a vrať ho. Pro použití, kde mezera v tabulce může znamenat problém (protože pravděpodobnost prostě identická být MUSÍ).
Teoreticky by to co navrhuje Justas [1] šlo pomocí prepared statements:
SET @randrow:=FLOOR((SELECT COUNT(*) FROM tbl)*RAND());
PREPARE STMT FROM 'SELECT * FROM tbl LIMIT ?, 1';
EXECUTE STMT USING @randrow;
Ale optimálnost prepared statements zmapovanou nemám, takže netuším jak moc je to lepší nebo horší než to co jsem navrhnul v článku. Nicméně jsem to tam pro úplnost přidal.
> V praktickém životě je to ale obvykle fuk.
Ja mam skoro problem predstavit si pripad, kdy to skutecne je fuk. Pokud se ma vybrat nekolik nahodnych produktu na zobrazeni v nejake postranni nabidce eshopu, tak pro naslednou analyzu navstevnosti bude docela zasadni problem, ze se to chova podle nejake zahadne pravdepodobnostni distribuce.
[7] Myslel jsem to jinak. V mém praktickém životě je třeba do tabulky jen přidávat řádky, maže se tak málo, a řádků je tak moc, že je úplně jedno jestli se jako náhodný některý řádek vybere s 4x větší pravděpodobností než jiné řádky. Když je v tabulce 10 milionů záznamů, je rozdíl mezi pravděpodobností 1/10000000 a 4/10000000 zanedbatelný (pro mě a pro mé potřeby). Uznávám, že praktické potřeby jiných lidí ovšem můžou být jiné.
[12] Pokud mi něco neuniká, tak dotaz vždycky vybere existující ID (bude <= max(id)). Problém bude samozřejmě s tou distribucí pravděpodobnosti. Osobně bych to řešil další tabulkou, která bude mít vlastní ID a jako cizí klíč ID té hlavní tabulky. Za normálních okolností nemůže být velký problém tu tabulku přehrnout a distribuci tím srovnat. V dotazu bude navíc jeden join, no.
Nechapem, nad cim sa pozastavujete.. spravi sa to na dva dotazy:
1.) Prvy dotaz pre nahodnu skalarnu hodnotu offsetu:
SELECT FLOOR((SELECT COUNT(*) FROM `mytable`) * RAND());
2.) Druhy dotaz s pouzitim LIMIT nema ziadny problem s "dierami".. za XX dosadime hodnotu z prveho dotazu:
SELECT * FROM `mytable` LIMIT XX, 1;
zcela náhodně vygenerovat pořadí položky v LIMIT a to opakovat tak dlouho, než se povede strefit do tabulky. Ošetření chyby by mohlo být rychlejší, než prohledávání v databázi (pro velké tabulky). Na vyhledání délky by mohl být použit algoritmus jakéhosi binárního hledání. Tedy je-li vygenerované pořadí za koncem tabulky, tak ho podělím dvěmi a zkusím znovu ...
Tomáš je autorem několika více či méně známých projektů jak z oblasti operačních systémů, tak internetu. V současnosti samozvaný expert na Linux, Bash, PHP a MySQL.
Přečteno 24 973×
Přečteno 23 532×
Přečteno 19 131×
Přečteno 17 852×
Přečteno 12 725×