sql >> Databasteknik >  >> RDS >> Mysql

Mycket enkel AVG() aggregeringsfråga på MySQL-servern tar löjligt lång tid

För att räkna antalet rader med ett specifikt datum måste MySQL lokalisera det värdet i indexet (vilket är ganska snabbt, trots allt är det vad index är gjorda för) och sedan läsa de efterföljande posterna av indexet em> tills den hittar nästa datum. Beroende på datatypen för esi , kommer detta att summera till att läsa några MB data för att räkna dina 700k rader. Att läsa en del MB tar inte mycket tid (och den data kan till och med redan vara cachad i buffertpoolen, beroende på hur ofta du använder indexet).

För att beräkna medelvärdet för en kolumn som inte ingår i indexet kommer MySQL återigen att använda indexet för att hitta alla rader för det datumet (samma som tidigare). Men dessutom, för varje rad den hittar, måste den läsa den faktiska tabelldatan för den raden, vilket innebär att använda primärnyckeln för att lokalisera raden, läsa några bytes och upprepa detta 700 000 gånger. Denna "slumpmässig åtkomst" är mycket långsammare än den sekventiella läsningen i det första fallet. (Detta blir värre av problemet att "några bytes" är innodb_page_size (16KB som standard), så du kan behöva läsa upp till 700k * 16KB =11GB, jämfört med "några MB" för count(*); och beroende på din minneskonfiguration kanske en del av dessa data inte cachelagras och måste läsas från disken.)

En lösning på detta är att ta med alla använda kolumner i indexet (ett "täckande index"), t.ex. skapa ett index på date, 01 . Då behöver MySQL inte komma åt själva tabellen, och kan fortsätta, i likhet med den första metoden, genom att bara läsa indexet. Storleken på indexet kommer att öka lite, så MySQL kommer att behöva läsa "någon mer MB" (och utföra avg -operation), men det bör fortfarande vara en fråga om sekunder.

I kommentarerna nämnde du att du måste beräkna snittet över 24 kolumner. Om du vill beräkna avg för flera kolumner samtidigt skulle du behöva ett täckande index på alla, t.ex. date, 01, 02, ..., 24 för att förhindra bordsåtkomst. Var medveten om att ett index som innehåller alla kolumner kräver lika mycket lagringsutrymme som själva tabellen (och det kommer att ta lång tid att skapa ett sådant index), så det kan bero på hur viktig den här frågan är om den är värd dessa resurser.

För att undvika MySQL-gränsen på 16 kolumner per index , kan du dela upp det i två index (och två frågor). Skapa t.ex. indexen date, 01, .., 12 och date, 13, .., 24 , använd sedan

select * from (select `date`, avg(`01`), ..., avg(`12`) 
               from mytable where `date` = ...) as part1
cross join    (select avg(`13`), ..., avg(`24`) 
               from mytable where `date` = ...) as part2;

Se till att dokumentera detta väl, eftersom det inte finns någon uppenbar anledning att skriva frågan på detta sätt, men det kan vara värt det.

Om du bara gör ett genomsnitt över en enda kolumn kan du lägga till 24 separata index (på date, 01 , date, 02 , ...), även om de totalt kommer att kräva ännu mer utrymme, men kan vara lite snabbare (eftersom de är mindre individuellt). Men buffertpoolen kan fortfarande gynna hela indexet, beroende på faktorer som användningsmönster och minneskonfiguration, så du kanske måste testa det.

Sedan date är en del av din primärnyckel kan du också överväga att ändra primärnyckeln till date, esi . Om du hittar datumen med den primära nyckeln, skulle du inte behöva ett extra steg för att komma åt tabelldata (eftersom du redan kommer åt tabellen), så beteendet skulle likna det täckande indexet. Men detta är en betydande förändring av din tabell och kan påverka alla andra frågor (som t.ex. använder esi för att lokalisera rader), så det måste övervägas noggrant.

Som du nämnde skulle ett annat alternativ vara att bygga en sammanfattningstabell där du lagrar förberäknade värden, speciellt om du inte lägger till eller ändrar rader för tidigare datum (eller kan hålla dem uppdaterade med en utlösare).



  1. mysql summa, med rad-ID

  2. Hur man förstår SQL Server Geografi Data Type

  3. Hur ansluter jag till MySQL Database?

  4. Avmystifierar CXPACKET- och CXCONSUMER-vänttyper i SQL Server