sql >> Database >  >> RDS >> Mysql

Zeer eenvoudige AVG()-aggregatiequery op MySQL-server duurt belachelijk lang

Om het aantal rijen met een specifieke datum te tellen, moet MySQL die waarde in de index lokaliseren (wat behoorlijk snel is, daar zijn indexen tenslotte voor) en vervolgens de daaropvolgende invoer van de index lezen em> totdat hij de volgende datum vindt. Afhankelijk van het datatype van esi , komt dit neer op het lezen van enkele MB aan gegevens om uw 700.000 rijen te tellen. Het lezen van enkele MB kost niet veel tijd (en die gegevens kunnen zelfs al in de bufferpool zijn opgeslagen, afhankelijk van hoe vaak u de index gebruikt).

Om het gemiddelde te berekenen voor een kolom die niet in de index is opgenomen, zal MySQL opnieuw de index gebruiken om alle rijen voor die datum te vinden (hetzelfde als voorheen). Maar bovendien moet het voor elke gevonden rij de werkelijke tabelgegevens voor die rij lezen, wat betekent dat de primaire sleutel moet worden gebruikt om de rij te lokaliseren, enkele bytes moet lezen en dit 700.000 keer moet herhalen. Deze "willekeurige toegang" is veel langzamer dan de sequentiële uitlezing in het eerste geval. (Dit wordt erger door het probleem dat "sommige bytes" de innodb_page_size (16 KB standaard), dus je moet mogelijk tot 700k * 16KB =11GB lezen, vergeleken met "wat MB" voor count(*); en afhankelijk van uw geheugenconfiguratie is het mogelijk dat sommige van deze gegevens niet in de cache worden opgeslagen en van schijf moeten worden gelezen.)

Een oplossing hiervoor is om alle gebruikte kolommen in de index op te nemen (een "dekkingsindex"), b.v. maak een index op date, 01 . Dan heeft MySQL geen toegang nodig tot de tabel zelf en kan het verder gaan, vergelijkbaar met de eerste methode, door alleen de index te lezen. De grootte van de index zal een beetje toenemen, dus MySQL zal "wat meer MB" moeten lezen (en de avg uitvoeren -operatie), maar het zou nog steeds een kwestie van seconden moeten zijn.

In de opmerkingen zei je dat je het gemiddelde over 24 kolommen moet berekenen. Als u de avg . wilt berekenen voor meerdere kolommen tegelijk heeft u op alle kolommen een dekkingsindex nodig, b.v. date, 01, 02, ..., 24 om toegang tot de tafel te voorkomen. Houd er rekening mee dat een index die alle kolommen bevat evenveel opslagruimte nodig heeft als de tabel zelf (en het zal lang duren om een ​​dergelijke index te maken), dus het kan afhangen van hoe belangrijk deze query is of deze die bronnen waard is.

Om de MySQL-limiet van 16 kolommen per index te vermijden , kunt u het in twee indexen (en twee query's) splitsen. Maak bijv. de indexen date, 01, .., 12 en date, 13, .., 24 , gebruik dan

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;

Zorg ervoor dat u dit goed documenteert, want er is geen duidelijke reden om de zoekopdracht op deze manier te schrijven, maar het kan de moeite waard zijn.

Als u altijd maar gemiddeld over een enkele kolom neemt, kunt u 24 afzonderlijke indexen toevoegen (op date, 01 , date, 02 , ...), hoewel ze in totaal nog meer ruimte nodig hebben, maar misschien een beetje sneller zijn (omdat ze afzonderlijk kleiner zijn). Maar de bufferpool kan nog steeds de voorkeur geven aan de volledige index, afhankelijk van factoren zoals gebruikspatronen en geheugenconfiguratie, dus het kan zijn dat u deze moet testen.

Sinds date deel uitmaakt van uw primaire sleutel, kunt u ook overwegen de primaire sleutel te wijzigen in date, esi . Als u de datums vindt met de primaire sleutel, heeft u geen extra stap nodig om toegang te krijgen tot de tabelgegevens (omdat u de tabel al opent), dus het gedrag zou vergelijkbaar zijn met de dekkingsindex. Maar dit is een belangrijke wijziging in uw tabel en kan van invloed zijn op alle andere zoekopdrachten (die bijvoorbeeld esi gebruiken om rijen te lokaliseren), dus het moet zorgvuldig worden overwogen.

Zoals je al zei, zou een andere optie zijn om een ​​overzichtstabel te maken waarin je vooraf berekende waarden opslaat, vooral als je geen rijen toevoegt of wijzigt voor datums uit het verleden (of ze up-to-date kunt houden met een trigger).



  1. 2 manieren om een ​​tabel op een gekoppelde server te maken met T-SQL

  2. MySql:Meerdere identieke dynamische tabellen opvragen

  3. cc1:fout:niet-herkende opdrachtregeloptie -Wno-null-conversie bij het installeren van python-mysql op mac 10.7.5

  4. Oneindig scrollen Alle items tegelijk laden?