sql >> Database >  >> RDS >> Mysql

MySQL MyISAM slow count() query ondanks dekking index

Dit is wat er aan de hand is.

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

zal de index gebruiken, die een Btree is die los staat van de gegevens. Maar het scant het op deze manier:

  1. Zoek het eerste item met icd='25000'. Dit is bijna onmiddellijk.
  2. Scan vooruit totdat als een verandering in icd wordt gevonden. Hiermee wordt alleen de index gescand en worden de gegevens niet aangeraakt. Volgens de EXPLAIN zullen er ongeveer 910.104 indexitems zijn om te scannen.

Laten we nu eens kijken naar de Btree voor die index. Op basis van de velden in de index zal elke rij precies 22 bytes zijn, plus wat overhead (schatting 40%). Een MyISAM-indexblok is 1 KB (cf. InnoDB's 16 KB). Ik schat 33 rijen per blok. 910,104/33 zegt dat er ongeveer 27K blokken moeten worden gelezen om de COUNT te doen. (Let op COUNT(core_id) moet core_id controleren omdat het null is, COUNT(*) doet niet; dit is een klein verschil.) Het lezen van 27K blokken op een gewone harde schijf duurt ongeveer 270 seconden. Je had geluk dat het binnen 60 seconden klaar was.

De tweede run vond al die blokken in de key_buffer (ervan uitgaande dat key_buffer_size ten minste 27 MB is), dus het hoefde niet op de schijf te wachten. Daarom ging het veel sneller. (Hiermee negeert u de Query-cache, die u zo wijs had door te spoelen of SQL_NO_CACHE te gebruiken.)

5.6 is toevallig niet relevant (maar bedankt voor het vermelden), aangezien dit proces niet is veranderd sinds 4.0 of eerder (behalve dat utf8 niet bestond; daarover hieronder meer).

Overschakelen naar InnoDB zou op een aantal manieren helpen. De PRIMAIRE SLEUTEL zou worden 'geclusterd' met de gegevens, niet opgeslagen als een afzonderlijke BTree. Dus zodra de gegevens of de PK in de cache zijn opgeslagen, is de andere onmiddellijk beschikbaar. Het aantal blokken zou meer op 5K lijken, maar het zouden blokken van 16 KB zijn. Deze kunnen sneller worden geladen als de cache koud is.

U vraagt:"Heb ik alleen een index op icd nodig?" -- Dat zou de MyISAM BTree-grootte verkleinen tot ongeveer 21 bytes per rij, dus de Btree zou ongeveer 21/27ste van de grootte zijn, niet veel verbetering (tenminste voor de cold-cache-situatie).

Een andere gedachte is, als icd is altijd numeriek en altijd numeriek, om MEDIUMINT UNSIGNED te gebruiken , en ga over op ZEROFILL als het voorloopnullen mag hebben.

Oeps, ik heb de KARAKTERSET niet opgemerkt. (Ik heb de bovenstaande cijfers aangepast, maar laat me dit nader toelichten.)

  • CHAR(5) staat 5 tekens toe .
  • ascii duurt 1 byte per karakter .
  • utf8 duurt maximaal 3 bytes per tekens .
  • Dus, CHAR(5) CHARACTER SET utf8 duurt 15 bytes altijd .

De kolom wijzigen in CHAR(5) CHARACTER SET ascii zou het verkleinen tot 5 bytes.

Als u het wijzigt in MEDIUMINT UNSIGNED ZEROFILL, wordt het teruggebracht tot 3 bytes.

Het verkleinen van de gegevens zou I/O versnellen met een ongeveer evenredige hoeveelheid (na nog eens 6 bytes toe te staan ​​voor de andere twee velden.



  1. Oracle Trigger ORA-04098:trigger is ongeldig en hervalidatie mislukt

  2. probleem met externe sleutel bij het vullen van de database

  3. SELECTEER * FROM MySQL Linked Server met SQL Server zonder OpenQuery

  4. MySQL-externe sleutel om NULL toe te staan?