WN

WN (https://www.wn.se/forum/index.php)
-   Serversidans teknologier (https://www.wn.se/forum/forumdisplay.php?f=4)
-   -   Mysql random verkar inte vara tillräckligt bra? (https://www.wn.se/forum/showthread.php?t=1065499)

naak2803 2015-09-10 15:35

Mysql random verkar inte vara tillräckligt bra?
 
Hej,
jag har en quiz-motor med över 1000 frågor.
Men jag tycker inte att SQL-satsen fungerar dåligt och med dåligt menar jag att den inte slumpar tillräckligt slumpmässigt... Har ni några andra förslag?

Kod:

SELECT * FROM dbo.quiz WHERE category = 445 LIMIT 20 ORDER BY RND()
Krav som jag har:
- Slumpa random
– Samma rad ska inte dyka upp flera ggr i samma fråga.

Cos 2015-09-10 23:37

Skapa en array med med unika IDn att hämta istället kanske? Validera att den innehåller unika värden innan sql frågan ställs.
Så har jag gjort och funkat helt OK.

Dock är jag osäker på vilken metod som mest resurskrävande, men de lär vara hugget som stucket på en mindre skala.

Nerix 2015-09-11 00:46

Citat:

Ursprungligen postat av naak2803 (Inlägg 20512653)
- Slumpa random
– Samma rad ska inte dyka upp flera ggr i samma fråga.

De låter snarare som att din query är lite knasig om samma fråga dyker upp flera gånger då ja tvivlar på att RND-funktionen inte skulle vara uniform.

Kan du posta hela queryn?

Citat:

Ursprungligen postat av Cos (Inlägg 20512661)
Skapa en array med med unika IDn att hämta istället kanske? Validera att den innehåller unika värden innan sql frågan ställs.
Så har jag gjort och funkat helt OK.

Dock är jag osäker på vilken metod som mest resurskrävande, men de lär vara hugget som stucket på en mindre skala.

Nej, de är en riktigt långsam lösning.

allstars 2015-09-11 08:41

långsam eller inte, så känns den lösningen bättre;

Kod:

SELECT ID FROM dbo.quiz WHERE category = 445 LIMIT 20 ORDER BY RND()
Om du ställer samma fråga flera gånger (i phpmyadmin eller motsvarande), blir det ofta att samma frågor återkommer?

Jag skulle nog ställa en fråga för att hämta alla 20 frågors ID, spara dem i en session och när de behövs hämta all fråge-data.

naak2803 2015-09-11 09:42

Citat:

Om du ställer samma fråga flera gånger (i phpmyadmin eller motsvarande), blir det ofta att samma frågor återkommer?
Exakt, det är just det som är problemet, inte att den inte slumpar slumpmässigt utan att om man ställer samma fråga flera ggr så är det oftast samma frågor som dyker upp, fast det finns 1000 frågor att välja mellan.

coredev 2015-09-11 09:47

Citat:

Ursprungligen postat av naak2803 (Inlägg 20512653)
Hej,
jag har en quiz-motor med över 1000 frågor.
Men jag tycker inte att SQL-satsen fungerar dåligt och med dåligt menar jag att den inte slumpar tillräckligt slumpmässigt... Har ni några andra förslag?

Kod:

SELECT * FROM dbo.quiz WHERE category = 445 LIMIT 20 ORDER BY RND()
Krav som jag har:
- Slumpa random
– Samma rad ska inte dyka upp flera ggr i samma fråga.

Som flera andra redan har varit inne på, gör på följande sätt:
1) Hämta unika fråge-id:n från databasen:
Kod:

SELECT id FROM dbo.quiz WHERE category = 445
2) Slumpa och begränsa dessa i ditt applikationslager (php, asp, jsp, etc).

3) Hämta sedan frågorna via ytterligare en SQL:
Kod:

SELECT column1, column2, etc... FROM dbo.quiz WHERE id in (1, 7, 32, 94, etc...)
Du får visseligen en ytterligare fråga till databasen men resultatet från den kan du enkelt cacha i ditt applikationslager.

Clarence 2015-09-11 14:28

Citat:

Ursprungligen postat av naak2803 (Inlägg 20512667)
Exakt, det är just det som är problemet, inte att den inte slumpar slumpmässigt utan att om man ställer samma fråga flera ggr så är det oftast samma frågor som dyker upp, fast det finns 1000 frågor att välja mellan.

Förmodligen beror det isåfall antingen på någon cache på SQL-frågan, dock tror jag standardlösningen med inbyggda query cache ska inaktiveras automatiskt vid en RAND(). Alternativt är MySQL en version där RAND() var buggat.

Vidare har du, förutsatt att du använder MySQL av något sånär modernt snitt, inte postat din riktiga query varpå det blir svårt att hjälpa till:
- RND() är ingen inbyggd funktion i MySQL
- LIMIT kan inte läggas innan ORDER BY

Vidare är det väldigt ineffektivt sätt att hämta slumpmässiga rader att köra en SELECT * FROM ... ORDER BY RAND() LIMIT x. Queryn behöver göra en full table scan och skalar därmed inte alls. För att göra den 50 ggr effektivare kan du lösa det med en subquery där du kör en SELECT <primary key> FROM table ORDER BY RAND() LIMIT x varpå du iallalfall bara behöver läsa hela primärnyckeln (oftast från minnet). Men vill du faktiskt göra det effektivt får du titta på riktigt alternativ istället - Googla "order by rand() alternatives" och hitta en metod som fungerar i ditt case.

jayzee 2015-09-11 14:31

Här har du en bra artikel samt förslag på bäst approach: http://akinas.com/pages/en/blog/mysql_random_row/

Clarence 2015-09-11 15:29

Citat:

Ursprungligen postat av jayzee (Inlägg 20512680)
Här har du en bra artikel samt förslag på bäst approach: http://akinas.com/pages/en/blog/mysql_random_row/

Bra artikel kan diskuteras.

Hans "snabbaste lösning" är fullkomligt förskräcklig prestanda på när tabellen växer om den är InnoDB, men funkar säkert bra med MyISAM. Varför han har fått så skevt resultat framkommer inte men är det på grund av att han har ett lågt antal rader så bör hela testet ses som helt värdelöst.

Intressant nog verkar han lika ointresserad eller ovetande om det som att en jämförelse blir helt irrelevant om man inte gör den med olika antal rader/mängd data för tidsjämförelsen. Liksom man verkligen MÅSTE ange tabelltyp så fort man skriver en COUNT(*) i en benchmark för att det ska ge något över huvudtaget.

Lösning 2 och 4 kan dock ses som OK kompromisser i de flesta fall, även om man förlorar precision om man har gaps i sin primärnyckel. Och nej, lita inte på artikelns resultat för #2 - den är inte 5 ggr långsammare än #3 utan mycket snabbare - i de allra flesta fall.

Nerix 2015-09-11 23:39

Citat:

Ursprungligen postat av coredev (Inlägg 20512668)
Som flera andra redan har varit inne på, gör på följande sätt:
1) Hämta unika fråge-id:n från databasen:
Kod:

SELECT id FROM dbo.quiz WHERE category = 445
2) Slumpa och begränsa dessa i ditt applikationslager (php, asp, jsp, etc).

3) Hämta sedan frågorna via ytterligare en SQL:
Kod:

SELECT column1, column2, etc... FROM dbo.quiz WHERE id in (1, 7, 32, 94, etc...)
Du får visseligen en ytterligare fråga till databasen men resultatet från den kan du enkelt cacha i ditt applikationslager.

En dålig och ickeskalbar lösning som dessutom introducerar ett nytt problem; cache invalidation. Databasen är designad för att snabbt lösa just sådana här problem.

Enklast är om TS bara postar hela sin query. Hen gör förmodligen någon märklig JOIN vilket ställer till problem.

naak2803 2015-09-12 12:47

Citat:

Ursprungligen postat av Nerix (Inlägg 20512692)
En dålig och ickeskalbar lösning som dessutom introducerar ett nytt problem; cache invalidation. Databasen är designad för att snabbt lösa just sådana här problem.

Enklast är om TS bara postar hela sin query. Hen gör förmodligen någon märklig JOIN vilket ställer till problem.


SELECT * FROM tp_question INNER JOIN tp_exam_tp_question ON tp_question.Id=tp_exam_tp_question.tp_questionsId WHERE tp_examId = 445 ORDER BY RAND() LIMIT 50

Nerix 2015-09-12 12:56

Citat:

Ursprungligen postat av naak2803 (Inlägg 20512706)
SELECT * FROM tp_question INNER JOIN tp_exam_tp_question ON tp_question.Id=tp_exam_tp_question.tp_questionsId WHERE tp_examId = 445 ORDER BY RAND() LIMIT 50

Nu vet jag inte exakt hur dina nycklar och data ser ut, så kan bara gissa. Me när du joinar med `tp_exam_tp_question` så dyker `tp_question.Id` förmodligen upp ett flertal gånger. Du väljer sedan slumpmässigt från frågor (tp_question.Id) som finns med fler gånger än andra i tabellen.

Vi kan verifiera min teori genom att du postar resultatet från följande query.

Kod:

SELECT tp_question.Id FROM tp_question INNER JOIN tp_exam_tp_question ON tp_question.Id=tp_exam_tp_question.tp_questionsId WHERE tp_examId = 445 ORDER BY tp_question.Id ASC LIMIT 50
Se till att välja ett `tp_examId` så att du får med så många resultat som möjligt så att vi kan se att duplikat följer med.

Notera att en GROUP BY inte är lösning på din problem.

tec 2015-09-13 22:24

Citat:

Ursprungligen postat av naak2803 (Inlägg 20512653)
Hej,
jag har en quiz-motor med över 1000 frågor.
Men jag tycker inte att SQL-satsen fungerar dåligt och med dåligt menar jag att den inte slumpar tillräckligt slumpmässigt... Har ni några andra förslag?

Kod:

SELECT * FROM dbo.quiz WHERE category = 445 LIMIT 20 ORDER BY RND()
Krav som jag har:
- Slumpa random
– Samma rad ska inte dyka upp flera ggr i samma fråga.

Så pass lite data att du lika gärna kan plocka ut alla rader och kasta om dem utanför Mysql. RAND() är otroligt slö.

Kod:

<?php
$data = mysql_fetch_object("SELECT * FROM dbo.quiz WHERE category = 445");
shuffle($data);
$results = array_slice($data, 0, 20);


Nerix 2015-09-13 23:09

Citat:

Ursprungligen postat av tec (Inlägg 20512750)
Så pass lite data att du lika gärna kan plocka ut alla rader och kasta om dem utanför Mysql. RAND() är otroligt slö.

Kod:

<?php
$data = mysql_fetch_object("SELECT * FROM dbo.quiz WHERE category = 445");
shuffle($data);
$results = array_slice($data, 0, 20);


Din lösning är per definition ännu slöare. RAND() räkna alla element innan några väljs ut vilket tar O(n) tid. Din lösning kräver också O(n) läsningar + tiden det tar att hämta och skicka informationen mellan server och klient + bearbetning av datan.

coredev 2015-09-14 08:09

Citat:

Ursprungligen postat av Nerix (Inlägg 20512692)
En dålig och ickeskalbar lösning som dessutom introducerar ett nytt problem; cache invalidation. Databasen är designad för att snabbt lösa just sådana här problem.

Jag förstår din poäng, men alla lösningar behöver inte vara skalbara. Hur många frågor kan OP förväntas ha?

Min arkitekturella utgångspunkt är att databasen skall göra så lite som möjligt - bättre att den gör det den är bra på och så låter man applikationslagret sköta resten. Enligt mig är OPs fall ett gränsfall, kanske är MySQL:s RAND-funktion bra nog för just detta fallet men i det stora hela är RAND inte designad för att skapa slumpmässiga tal av hög kvalité: "RAND() is not meant to be a perfect random generator. It is a fast way to generate random numbers on demand that is portable between platforms for the same MySQL version."

Clarence 2015-09-14 09:26

För att illustrera lite hur pass dåliga de dåliga lösningarna faktiskt är. Kört med 7 miljoner ca 100-150 byte rader i en InnoDB tabell på en medelmåttig hårdvara, med en kraftig maskin kan man förvänta sig 20-50% av exekveringstiden:

Exekveringstid ca 15s.
Kod:

SELECT * FROM table ORDER BY RAND() LIMIT 1
Exekveringstid ca 15s. Detta ger inga random-rader men används i vissa, riktigt dåliga, alternativ till ORDER BY RAND().
Kod:

SELECT count(*) FROM table
Exekveringstid 0.02s. Kräver primary key (behöver inte heta id). Ger sämre resultat med större gaps i PK. Måste köras en gång per rad för hyfsad randomness (även om det givetvis går att kombinera i en query). Kan ge kopior varpå man måste ha kod för att hämta fler frågor om man får kopior. För många rader kan det effektiviseras ordentligt genom att lyfta ut id-generation (antingen i en stored procedure eller i kod).
Kod:

SELECT *
FROM table
WHERE id >= FLOOR(1 + RAND() * (SELECT MAX(id) FROM table))
LIMIT 1

Detta är inte de enda alternativen utan bara en illustration till hur enorm skillnad det gör. Problemet med att använda ORDER BY RAND() ligger inte i hur pass exakt RAND() är utan hur pass fruktansvärd prestandan är vid ORDER BY RAND() på något annat än ett VÄLDIGT litet resultset.

tec 2015-09-14 12:01

Citat:

Ursprungligen postat av Nerix (Inlägg 20512752)
Din lösning är per definition ännu slöare. RAND() räkna alla element innan några väljs ut vilket tar O(n) tid. Din lösning kräver också O(n) läsningar + tiden det tar att hämta och skicka informationen mellan server och klient + bearbetning av datan.

Körs totalt på 0.01s, 1000 rader motsvarande quizfrågor. Undviker att använda RAND(), som i sig är slö och som TS hade problem med.

linusoleander 2015-09-14 15:28

Citat:

Ursprungligen postat av tec (Inlägg 20512770)
Körs totalt på 0.01s, 1000 rader motsvarande quizfrågor. Undviker att använda RAND(), som i sig är slö och som TS hade problem med.

Värt å tillägga är att din lösning inte löser problemet då de verkar som att TS query returnerar dubletter. De är således inget fel på RAND() utan queryn i sig.


Alla tider är GMT +2. Klockan är nu 19:27.

Programvara från: vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Svensk översättning av: Anders Pettersson