sql >> Database teknologi >  >> RDS >> Mysql

MySQL MyISAM slow count()-forespørgsel trods dækkende indeks

Her er, hvad der foregår.

The SELECT COUNT (...) icd_index where icd='25000'

vil bruge indekset, som er et BTree adskilt fra dataene. Men den scanner den på denne måde:

  1. Find den første post med icd='25000'. Dette er næsten øjeblikkeligt.
  2. Scan frem, indtil hvis finder en ændring i icd. Dette scanner kun indekset og berører ikke dataene. Ifølge EXPLAIN vil der være omkring 910.104 indeksposter at scanne over.

Lad os nu se på BTree for det indeks. Baseret på felterne i indekset vil hver række være præcis 22 bytes, plus der vil være nogle overhead (skøn 40%). En MyISAM-indeksblok er 1KB (jf. InnoDB's 16KB). Jeg vil anslå 33 rækker pr. blok. 910.104/33 siger, at omkring 27K blokke skal læses for at udføre COUNT. (Bemærk COUNT(core_id) skal tjekke core_id for at være nul, COUNT(*) gør ikke; dette er en mindre forskel.) At læse 27K blokke på en almindelig harddisk tager omkring 270 sekunder. Du var heldig at få det gjort på 60 sekunder.

Den anden kørsel fandt alle disse blokke i key_buffer (forudsat at key_buffer_size er mindst 27MB), så den behøvede ikke at vente på disken. Derfor var det meget hurtigere. (Dette ignorerer Query-cachen, som du havde visdom til at tømme eller bruge SQL_NO_CACHE.)

5.6 er tilfældigvis irrelevant (men tak for at nævne det), da denne proces ikke har ændret sig siden 4.0 eller før (bortset fra at utf8 ikke eksisterede; mere om det nedenfor).

At skifte til InnoDB ville hjælpe på et par måder. Den PRIMÆRE NØGLE ville blive 'grupperet' med dataene, ikke gemt som et separat BTree. Derfor, når først dataene eller PK'en er cachelagret, er den anden umiddelbart tilgængelig. Antallet af blokke ville være mere som 5K, men de ville være 16KB blokke. Disse kan muligvis indlæses hurtigere, hvis cachen er kold.

Du spørger "Har jeg brug for et indeks på icd alene?" -- Nå, det ville krympe MyISAM BTree-størrelsen til omkring 21 bytes pr. række, så BTree ville være omkring 21/27-del af størrelsen, ikke meget forbedring (i hvert fald for cold-cache situation).

En anden tanke er, hvis icd er altid numerisk og altid numerisk, for at bruge MEDIUMINT UNSIGNED , og sæt på ZEROFILL hvis den kan have foranstillede nuller.

Ups, jeg kunne ikke bemærke CHARACTER SET. (Jeg har rettet tallene ovenfor, men lad mig uddybe.)

  • CHAR(5) tillader 5 tegn .
  • ascii tager 1 byte pr. tegn .
  • utf8 tager op til 3 bytes pr. tegn .
  • Så CHAR(5) CHARACTER SET utf8 tager 15 bytes altid .

Ændring af kolonnen til CHAR(5) CHARACTER SET ascii ville formindske den til 5 bytes.

Ændring af den til MEDIUMINT UNSIGNED ZEROFILL ville formindske den til 3 bytes.

Formindskelse af dataene ville fremskynde I/O med en nogenlunde proportional mængde (efter at have tilladt yderligere 6 bytes for de to andre felter.



  1. Oracle SQL Developer - Fejl:FROM søgeord blev ikke fundet, hvor det forventes

  2. Opret en hjemmeside med MySQL

  3. PLS-00539 og PLS-00538 fejl ved oprettelse af UDT-funktioner

  4. Io-undtagelse:Oracle-fejl ORA-12650 efter opgradering af Oracle til 12g