sql >> Databasteknik >  >> RDS >> Mysql

MySQL MyISAM slow count()-fråga trots att det täcker index

Här är vad som händer.

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

kommer att använda indexet, som är ett BTree separat från data. Men den skannar den på detta sätt:

  1. Hitta den första posten med icd='25000'. Detta är nästan omedelbart.
  2. Skanna framåt tills om hittar en förändring i icd. Detta kommer endast att skanna in indexet, inte vidröra data. Enligt EXPLAIN kommer det att finnas cirka 910 104 indexposter att skanna över.

Låt oss nu titta på BTree för det indexet. Baserat på fälten i indexet kommer varje rad att vara exakt 22 byte, plus det kommer att finnas en del overhead (uppskattning 40%). Ett MyISAM-indexblock är 1KB (jfr InnoDB:s 16KB). Jag skulle uppskatta 33 rader per block. 910,104/33 säger att cirka 27K block måste läsas för att göra COUNT. (Notera COUNT(core_id) måste kontrollera core_id för att vara null, COUNT(*) gör inte; detta är en mindre skillnad.) Att läsa 27K block på en vanlig hårddisk tar cirka 270 sekunder. Du hade turen att få det gjort på 60 sekunder.

Den andra körningen hittade alla dessa block i key_buffer (förutsatt att key_buffer_size är minst 27MB), så den behövde inte vänta på disken. Därför gick det mycket snabbare. (Detta ignorerar Query-cachen, som du hade klokheten att tömma eller använda SQL_NO_CACHE.)

5.6 råkar vara irrelevant (men tack för att du nämner det), eftersom den här processen inte har förändrats sedan 4.0 eller tidigare (förutom att utf8 inte fanns, mer om det nedan).

Att byta till InnoDB skulle hjälpa på ett par sätt. Den PRIMÄRA NYCKELN skulle "klustras" med data, inte lagras som ett separat BTree. Så snart data eller PK är cachad är den andra omedelbart tillgänglig. Antalet block skulle vara mer som 5K, men de skulle vara 16KB block. Dessa kan laddas snabbare om cachen är kall.

Du frågar "Behöver jag enbart ett index på icd?" -- Tja, det skulle krympa MyISAM BTree-storleken till cirka 21 byte per rad, så BTree skulle vara cirka 21/27 av storleken, inte mycket förbättring (åtminstone för cold-cache situation).

En annan tanke är om icd är alltid numerisk och alltid numerisk, för att använda MEDIUMINT UNSIGNED , och slå på ZEROFILL om den kan ha inledande nollor.

Hoppsan, jag märkte inte TECKENSETTET. (Jag har fixat siffrorna ovan, men låt mig utveckla det.)

  • CHAR(5) tillåter 5 tecken .
  • ascii tar 1 byte per tecken .
  • utf8 tar upp till 3 byte per tecken .
  • Så, CHAR(5) CHARACTER SET utf8 tar 15 byte alltid .

Ändra kolumnen till CHAR(5) CHARACTER SET ascii skulle krympa den till 5 byte.

Om du ändrar den till MEDIUMINT UNSIGNED ZEROFILL skulle den krympa till 3 byte.

Att krympa data skulle påskynda I/O med en ungefär proportionell mängd (efter att ha tillåtit ytterligare 6 byte för de andra två fälten.



  1. Hur kan jag korrigera MySQL Load Error

  2. Bästa MySQL-prestandajusteringsverktyget?

  3. misslyckas med att ladda ROracle:det går inte att ladda delat objekt ROracle.so:libclntsh.so.11.1 Ingen sådan fil eller katalog

  4. Vilka är prestandakonsekvenserna av Oracle IN-klausul utan anslutningar?