For at tælle antallet af rækker med en specifik dato, skal MySQL finde denne værdi i indekset (hvilket er ret hurtigt, det er trods alt det, indekser er lavet til) og derefter læse de efterfølgende poster af indekset em> indtil den finder næste dato. Afhængigt af datatypen for esi
, vil dette opsummere til at læse nogle MB data for at tælle dine 700.000 rækker. At læse nogle MB tager ikke meget tid (og de data kan endda allerede være cachelagret i bufferpuljen, afhængigt af hvor ofte du bruger indekset).
For at beregne gennemsnittet for en kolonne, der ikke er inkluderet i indekset, vil MySQL igen bruge indekset til at finde alle rækker for den dato (det samme som før). Men derudover, for hver række, den finder, skal den læse de faktiske tabeldata for den række, hvilket betyder at bruge den primære nøgle til at finde rækken, læse nogle bytes og gentage dette 700k gange. Denne "tilfældig adgang"
er meget langsommere end den sekventielle læsning i det første tilfælde. (Dette bliver værre af problemet med, at "nogle bytes" er innodb_page_size
(16KB som standard), så du skal muligvis læse op til 700k * 16KB =11GB, sammenlignet med "nogle MB" for count(*)
; og afhængigt af din hukommelseskonfiguration er nogle af disse data muligvis ikke cachelagret og skal læses fra disken.)
En løsning på dette er at medtage alle brugte kolonner i indekset (et "dækkende indeks"), f.eks. opret et indeks på date, 01
. Så behøver MySQL ikke at få adgang til selve tabellen, og kan fortsætte, i lighed med den første metode, ved blot at læse indekset. Størrelsen på indekset vil stige en smule, så MySQL bliver nødt til at læse "noget mere MB" (og udføre avg
-operation), men det burde stadig være et spørgsmål om sekunder.
I kommentarerne nævnte du, at du skal beregne gennemsnittet over 24 kolonner. Hvis du vil beregne avg
for flere kolonner på samme tid, skal du have et dækkende indeks på dem alle, f.eks. date, 01, 02, ..., 24
for at forhindre bordadgang. Vær opmærksom på, at et indeks, der indeholder alle kolonner, kræver lige så meget lagerplads som selve tabellen (og det vil tage lang tid at oprette et sådant indeks), så det kan afhænge af, hvor vigtig denne forespørgsel er, om den er disse ressourcer værd.
For at undgå MySQL-grænsen på 16 kolonner pr. indeks
, kan du opdele det i to indekser (og to forespørgsler). Opret f.eks. indekserne date, 01, .., 12
og date, 13, .., 24
, og brug derefter
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;
Sørg for at dokumentere dette godt, da der ikke er nogen åbenlys grund til at skrive forespørgslen på denne måde, men det kan være det værd.
Hvis du kun sætter et gennemsnit over en enkelt kolonne, kan du tilføje 24 separate indekser (på date, 01
, date, 02
, ...), selvom de i alt vil kræve endnu mere plads, men kan være en smule hurtigere (da de er mindre individuelt). Men bufferpuljen kan stadig favorisere det fulde indeks, afhængigt af faktorer som brugsmønstre og hukommelseskonfiguration, så du skal muligvis teste det.
Siden date
er en del af din primære nøgle, kan du også overveje at ændre den primære nøgle til date, esi
. Hvis du finder datoerne ved den primære nøgle, behøver du ikke et ekstra trin for at få adgang til tabeldataene (da du allerede har adgang til tabellen), så adfærden ville ligne det dækkende indeks. Men dette er en væsentlig ændring af din tabel og kan påvirke alle andre forespørgsler (som f.eks. bruger esi
for at lokalisere rækker), så det skal overvejes nøje.
Som du nævnte, ville en anden mulighed være at bygge en oversigtstabel, hvor du gemmer forudberegnede værdier, især hvis du ikke tilføjer eller ændrer rækker for tidligere datoer (eller kan holde dem opdaterede med en trigger).