HELIA TIKO-05 1 ( 12) Suorituskyky DBMS-järjestelmien keskeisiä laatuvaatimuksia ovat Tiedon luotettavuus (kattaen seuraavat: tietoturva, tiedon eheys, tiedon säilyvyys) Tiedon saatavuus (kattaen myös suorituskykyisen vastausajan) Skaalautuvuus (suorituskyky ei oleellisesti heikkene vaikka samanaikaisten käyttäjien määrä kasvaa). Vastausaika tarkoittaa aikaa, jossa käyttäjä saa työasemansa näytölle vastauksen antamalleen hae, talleta, suorita, tms tehtävälle, esimerkiksi SQL-kyselylleeen. Nykyteknologian aikana kohtuullisena vastausaikana pidetään noin 1-2 sekuntia. Puskuriallas, sivu, levyn käsittely DBMS käsittelee tietoa keskusmuistista varaamassaan puskurialtaassa (data cache, buffer pool). Puskurialtaan koko voi olla esimerkiksi 50 MB 15 GB eli pieni tietokanta voi mahtua kerralla puskurialtaaseen. Luotettava tiedon varastointi toteutetaan nykyisin levymuisteissa (disk), jonne kirjoittaminen ja josta lukeminen (IOtoiminta) on hidasta (hakuaika noin 10-30 ms) verrattuna keskusmuistissa nanosekunneissa tapahtuvaan tiedon käsittelynopeuteen. RDBMS: SQL-jäsentäjä Optimoija jne Puskuriallas (data cache) keskusmuistissa x Indeksisivuja ja taulusivuja Sivujen Sivujen hakuja talletuksia DBMS-järjestelmät pyrkivät tehostamaan levyjen käyttöä järjestämällä tiedon tallennuksen 2-32 tietokantatiedostoja KB kokoisille sivuille (page, Oracle: block). Tyypillinen sivun koko on nykyisin 8 KB (eli 8192 tavua). Sivujen järjestys ja sivuille talletettujen taulurivien hallinta hoidetaan DBMS:n sisäisillä tietorakenteilla. Levy-IO:n tehostamiseksi levykäsittely tapahtuu sivu kerrallaan, lukeminen puskurialtaaseen (peräkkäisluvussa) esimerkiksi 8 sivun erissä. Luetut sivut säilyvät pitkään puskurialtaassa ja niitä uudelleen tarvittaessa ne löytyvät loogisella luvulla puskurialtaasta sen sijaan, että tarvittaisiin fyysinen luku levyltä. Loogisten lukujen osuutta kaikista lukuoperaatioista sanotaan Hit Ratio ksi. Tämä on yleensä 95-99%. Muutetut sivut kirjoitetaan levyllä yleensä DBMS:n checkpoint-operaatioissa.
HELIA TIKO-05 2 ( 12) Indeksit Yksittäisen rivin etsiminen suuresta taulusta peräkkäishaulla levymuistista on hidasta ja ei onnistu kohtuullisessa vastausajassa. Kaikki keskeiset DBMS-järjestelmät käyttävätkin rivien paikallistamiseen indeksi- eli hakemistoratkaisuja. Indeksi (index) on erillinen hakemistotietorakenne, joka toteutetaan omilla esim 8 KB tietokantasivuillaan (esimerkki viereisessä kuvassa sovitettu kirjasta Gulutzan & Peltzer: SQL Performance Tuning). Indeksin David 1 juurisivu Otto 2 Susan 3 1 2 3 Barry 1.1 Joe 3.2 Sam 4.1 Indeksin Carla 4.3 Mark 1.2 Sid 3.1 sivut David 2.2 Otto 4.2 Sue 3.3 Susan 2.1 1 2 3 4 1.1: Barry 1.2: Mark 2.1: Susan 2.2: David 3.1: Sid 3.2: Joe 3.3: Sue 4.1: Sam 4.2: Otto 4.3: Carla Taulun sivut Taulun perusavaimelle (primary key) DBMS luo automaattisesti indeksin siten että jokaisen rivin perusavain ja rivin osoitetieto talletetaan riviä vastaavalle indeksitietueelle (index record). Indeksitietueet järjestetään avaimen suhteen indeksisivuille nousevaan järjestykseen. lisätään 103 ennen 100 123... 745 osoitteet vastaavien rivien taulusivuille jälkeen juurisivu 438 745 lehtitaso 100... 103 438... 450... 745 osoitteet vastaavien rivien taulusivuille Indeksisivun täyttyessä se jaetaan kahtia (ks kuva yllä) ja näiden sivujen yläpuolelle rakennetaan hakemistosivu, jonne kopioidaan molempien sivujen suurimmat avainarvot ja näiden sivujen osoitteet. Indeksisivuista muodostuu näin ns. B-puu -rakenne (B-tree), missä sivut linkitetään lehtitasolla ketjuksi ja lehtitason sivuja hallinnoidaan hierarkkisesti ylempien tasojen erillisillä hakemistosivuilla, joilta löytyy aina osoite kullekin alemman tason sivulle ja kyseisen sivun suurin avain. Hierarkkia jatkuu tasapainoitettuna puussa ylöspäin kunnes yksi indeksisivu riittää osoittamaan kaikki edellisen tason sivut. Tätä ylintä sivua sanotaan indeksin juurisivuksi.
HELIA TIKO-05 3 ( 12) Tavallinen B-puu kirjaa rivien osoitteet taulusivuilla (ks kuva Non-clustering Index) (Non-clustering) INDEX hakemistosivut ja avaimet juuri 50 82 95 12 32 50 58 70 82 89 95 6 8 12 15 18 32 35 40 50 51 58 60 62 70 71 78 82 83 85 89 91 95 lehtitaso taulutilan sivut ja taulun rivit Joissakin järjestelmissä voidaan käyttää myös taulun rivejä järjestäviä indeksejä: Clustering index pyrkii pitämään taulun rivit indeksin mukaisessa järjestyksessä (tällainen on esimerkiksi DB2:n toteutus) Clustering INDEX hakemistosivut ja avaimet juuri 50 82 95 12 32 50 58 70 82 89 95 6 8 12 15 18 32 35 40 50 51 58 60 62 70 71 78 82 83 85 89 91 95 lehtitaso taulutilan sivut ja taulun rivit Clustered index taulun rivi talletetaan indeksiin lehtitason sivuille (Microsoftin SQL Serverin oletus perusavaimen indeksille, Oraclen toteutuksessa tätä kutsutaan Indexorganized tauluksi).
HELIA TIKO-05 4 ( 12) Indeksit ja SQL ISO SQL-standardi ei tunne indeksin käsitettä, mutta todellisissa järjestelmissä indeksi luodaan yleensä seuraavaa muotoa olevalla komennolla CREATE [UNIQUE] INDEX <index name> ON <table> ( <column> [, ] ) missä <index name> on indeksin nimi, <table> on taulu, jolle indeksi luodaan, ja suluissa määritetään indeksiavain siten että <column> on indeksiin kustakin rivistä kopioitavan sarakkeen nimi. Jos indeksiavain muodostetaan useasta sarakkeesta, nämä erotetaan pilkuilla toisistaan. UNIQUE-määre tarkoittaa, että indeksi valvoo tauluun lisättävien rivien yksikäsitteisyyttä indeksiavaimen osalta. Jos tauluun yritetään lisätä rivi, jonka indeksiavain on jo indeksissä, ei DBMS hyväksy rivin lisäämistä. Perusavaimelle (PRIMARY KEY määreestä ) automaattisesti syntyvä indeksi on juuri tällainen UNIQUE-indeksi. Näin indeksillä on myös perusavaimen eheyttä valvova tehtävä. CREATE TABLE komennon mahdollisille UNIQUE-määreille järjestelmä luo yleensä automaattisesti UNIQUE-indeksin. Jos DBMS ei tätä tue, on meidän luotava itse UNIQUEindeksi yksikäsitteisyyttä valvomaan komennolla CREATE UNIQUE INDEX Liitosten ja viite-eheyssääntöjen suoritustehoa varten on yleensä hyödyllistä luoda indeksit viiteavaimille. Jotkut järjestelmät, kuten esimerkiksi suomalainen Solid, luovat automaattisesti indeksit myös CREATE TABLE komennon FOREIGN KEY määreiden perusteella. Toisioindeksit ja paksunnetut indeksit Kyselyn hakuehdossa voi olla myös muita kuin perus- tai viiteavaintietoja ja näille kannattaa harkita lisäindeksien eli toisioindeksien rakentamista. Jos sovelluksessa usein käytettävä kysely on raskas, kannattaa pohtia kaikkien hakuehdossa esiintyvien sarakkeiden kopiointia erilliseen indeksiin. Indeksiä, josta löytyvät kaikki kyselyssä tarvittavat tiedot sanotaan kattavaksi indeksiksi (covering index). Nykyaikaisen DBMS-järjestelmän indeksin rakenteesta ja tilantarpeesta saa kuvan seuraavasta Oracle 9.2 järjestelmän 1.000.000 asiakkaan CUST-taulun perusavaimelle rakentaman indeksin statistiikasta, kun käytetty blockin koko on 8 KB. Perusavaimen tietotyyppi tässä taulussa on CHAR(8) ja rivin osoitetieto (ROWID) vie 8 tavua.
HELIA TIKO-05 5 ( 12) Miljoonan rivin osoittamiseen on näin tarvittu 3-tasoinen indeksi, jossa on 3870 lehtitason sivua ja kahdella ylemmällä indeksitasolla 8 + 1 sivua. Kaikkiaan indeksin tilantarve on tässä 31.032.288 tavua, josta käytössä on 19.053.142 tavua. Indeksien käyttö SQL-kielessä ei indeksien käyttöön vaikuteta itse SQL- Indeksi ja saantitavat komennon määreillä, vaan indeksien käytöstä päättää index scan SQL-komennon analysoiva (koko lehtitaso) Indeksi range scan juurisivu (arvoväli) optimoija, joka on osa DBMS-järjestelmää, SQLkomennon muodon ja matching scan (suora haku) tauluista ja indekseistä (systeemitauluista) saatavissa olevien rakenne- ja Taulu tilastotietojen perusteella. Nopeimman saantitavan valinta monista mahdollisista table scan (koko taulun saantivaihtoehdoista voi olla läpikäynti) optimoijalle raskaskin laskentatehtävä. Järjestelmäkohtaisesti voidaan SQL-komentoon kuitenkin upottaa optimoijaa ohjaavia vihjeitä (hints). Indeksiä käyttäviä saantitapoja (data access) ovat Matching Scan eli yksilöity haku avaimella (esimerkiksi WHERE nro = 123 ) Range Scan eli haku annetulta avaimen arvoväliltä (esimerkiksi WHERE pvm BETWEEN DATE 2005-11-14 AND DATE 2005-12-01 ) Index Scan eli koko lehtitason läpikäynti. lehtitaso
HELIA TIKO-05 6 ( 12) Näiden vaihtoehtona on Table Scan eli koko taulun rivien luku peräkkäisjärjestyksessä. Pienien taulujen osalta tämä voi olla nopeinta, mutta suurten taulujen ollessa kyseessä yksilöity haku annetulla avaimella tai haku arvoväliltä, varsinkin kapealta arvoväliltä, tehostavat suoritusta ratkaisevasti. Koko indeksin lehtitason läpikäynti on tehokas vain jos kaikki kyselyn tarvitsemat tiedot saadaan indeksin tiedoista ilman taulusivuilla käyntejä. Järjestelmäkohtaisesti näille saantitavoille voi esiintyä hieman erilaisia nimiä. Optimoijan laskeman suoritussuunnitelman voi yleensä tarkistaa EXPLAIN PLAN FOR.. muotoa olevalla (tai jollakin vastaavalla järjestelmän) komennolla. Indeksien suunnittelusta Indeksejä suunniteltaessa kannattaa muistaa, että rivien lisääminen tauluun ja rivien poistaminen taulusta tarkoittavat taulun kaikkien indeksien ylläpitoa. Samoin kaikkien indekseissä esiintyvien sarakkeiden muutokset päivittävät automaattisesti vastaavia indeksejä. Kirjallisuudesta löytyvät vanhat säännöt joiden mukaan taululla saa olla korkeintaan 3-6 indeksiä eivät kuitenkaan päde enää yleisesti nykyjärjestelmissä. Joissakin sovelluksissa taululla voi olla esimerkiksi 15 indeksiä. Monisarakkeisen indeksin sarakkeiden järjestys indeksitietueilla on suunniteltava huolella. Ensimmäisillä indeksin sarakkeilla voidaan esimerkiksi indeksin Range Scan hakua rajoittaa merkitsevästi. Väärin muodostettu sarakejärjestys voi johtaa koko lehtitason läpikäyntiin, mikä yhdistettynä vielä suuren rivijoukon hakuun taulusivuilta romahduttaa suorituskyvyn. Mitä useammin kyselyä käytetään sitä tärkeämpää on sen suorituskyvyn säätö joko komennon muotoa korjaamalla tai indeksisuunnittelulla. Suuren rivijoukon lajittelu on edelleen raskas operaatio ja sopivassa järjestyksessä toteutettu indeksi voi poistaa erillisen lajittelun tarpeen. Joskus kannattaa raskasta kyselyä varten luoda jopa tilapäisiä indeksejä, jotka poistetaan kyselyn jälkeen. Indeksin poisto tapahtuu järjestelmästä riippuen komennolla tai DROP INDEX <index name> DROP INDEX <table name>.<index name> SQL-kyselyjen muodosta Optimoija ei hallitse hakuavainten käyttöä hakuehdon lausekkeissa, esimerkiksi WHERE (avain + 1) > 1000
HELIA TIKO-05 7 ( 12) Optimoijalle vaikeita hakuehtoja tulisi vältää. OR-operaattori on eräs tällainen. Näistä vältettävistä hakuehdoista (optimoijan sudenkuopista ) on yleensä DBMS-järjestelmien käsikirjoissa luettelo. Hakuehdon avainarvojen tietotyyppien tulee myös vastata sarakkeiden tietotyyppejä. Optimoijat kehittyvät jatkuvasti ja oppikirjojen tiedot voivat olla vanhentuneita. Eräs vanhentunut myytti on että sisäkysely olisi raskaampi kuin liitos. Tuoreimman tiedon saa tutkimalla itse todellisten järjestelmien optimoijien suoritussunnitelmia. Kirjallisuutta: Hovi, Huotari, Lahdenmäki: Tietokantojen suunnittelu ja indeksointi, Docendo 2003 (toinen painos 2005) Gulutzan, Peltzer: SQL Performance Tuning, Addison-Wesley, 2002
HELIA TIKO-05 8 ( 12) HARJOITUKSET Harjoitus 1. Info9-palvelimen Oracle 9.2-asennuksen TUX2-instanssissa on DBTech-käyttäjän schemassa taulut CUST, INVOICE ja INVOICEPLUS, joihin on myönnetty SELECT-oikeus PUBLICroolille eli ne näkyvät kaikille TUX2-instanssin käyttäjille. ICT03D-opintojakson osallistujilla on TUX2-instanssissa käyttäjätunnukset, joilla on oikeus luoda omia näkymiä. Luo näille DBTech-käyttäjän tauluille omat henkilökohtaiset näkymäsi: CREATE OR REPLACE VIEW Asiakas (asno,ryhma,etunimi,sukunimi,sukup,lahios,kaupunki,puh) AS SELECT CNo,CType,FName,LName,Sex,Address,City,Phone FROM DBTech.CUST; CREATE OR REPLACE VIEW Lasku (laskuno,asno,euroa,pvm,viite) AS SELECT Ino,CNo,IEur,IDate,IRefd FROM DBTech.INVOICE; CREATE OR REPLACE VIEW Lasku2 (laskuno,asno,euroa,pvm,viite,kaupunki) AS SELECT Ino,CNo,IEur,IDate,IRefd,City FROM DBTech.INVOICEPLUS; Taulujen saraketiedot voit selvittää komennoilla DESCRIBE DBTech.CUST; DESCRIBE DBTech.INVOICE; DESCRIBE DBTech.INVOICEPLUS; Luo henkilökohtainen PLAN_TABLE -taulu liitteen 1 ohjeen mukaan. Tutki kyselyn SELECT * FROM Asiakas WHERE asno = 12345; suoritussuunnitelma suunnilleen seuraavasti SQL> EXPLAIN PLAN FOR 2 SELECT * FROM Asiakas WHERE asno = 12345; SQL> select plan_table_output 2 from table(dbms_xplan.display('plan_table',null,'serial'));
HELIA TIKO-05 9 ( 12) PLAN_TABLE_OUTPUT -------------------------------------------------------------------- Id Operation Name Rows Bytes Cost -------------------------------------------------------------------- 0 SELECT STATEMENT 1 115 5681 * 1 TABLE ACCESS FULL CUST 1 115 5681 -------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- PLAN_TABLE_OUTPUT --------------------------------------------------------------------- 1 - filter(to_number("cust"."cno")=12345) Note: cpu costing is off 14 rows selected. Suoritussuunnitelman tekstistä TABLE ACCESS FULL (tarkoittaa Table Scan saantitapaa) nähdään, että optimoija ei osannut valita komennon suoritukseen taulun CUST perusavaimen CNO indeksiä, vaan on päätynyt koko taulun läpikäyntiin. Syy ei ole optimoijan, vaan syy löytyy SQL-komennosta mutta mikähän tässä mättää? Mitataan varmuuden vuoksi komennon suoritusaika seuraavalla komentosarjalla SET TIMING ON TIMING START SELECT * FROM Asiakas WHERE asno = 12345; TIMING STOP Huom. Ensimmäisen kokeilijan jälkeen suoritusajat arvattavasti nopeutuvat koska taulun sivut jäävät puskurialtaaseen ja seuraavat komennon suorittavat eivät juuri näe levy-io:n aiheuttamaa kuormaa. Todellinen suoritusaikainen suoritussuunnitelma ja sen suoritusstatistiikka (mm. fyysisten levylukujen määrä) nähdään TUX2-instanssiin rakennetun PLUSTRACE-roolin valtuuksilla komentosarjalla SET LINESIZE 101 SET AUTOTRACE ON timing start SELECT * FROM Asiakas WHERE asno = 12345; timing stop SET AUTOTRACE OFF Kuinka nopea suoritus on, kun SQL-komento korjataan käyttämään perusavaimen CUST_PKindeksiä?
HELIA TIKO-05 10 ( 12) Harjoitus 2. Lasku ja Lasku2 näkymien taulut ovat lähes samanlaiset. Lasku-näkymän taulun viiteavaimelle CNO, joka viittaa CUST-taulun perusavaimeen, on rakennettu indeksi Invoice_FK, mutta Lasku2-näkymän taululla vastaavaa indeksiä ei ole. Mittaa edellisen harjoituksen mallien perusteella kyselyn SELECT A.asno, etunimi, sukunimi, SUM(euroa) AS summa FROM Asiakas A JOIN Lasku L ON (A.asno=L.asno) WHERE A.asno = '00007903' GROUP BY A.asno, etunimi, sukunimi; suoritusaika ja katso suoritussunnitelma Aja vastaava mittaus myös käyttäen Lasku-näkymän asemesta näkymää Lasku2. Mikä on indeksin Invoice_FK vaikutus suoritukseen? Harjoitus 3. Perusta TUX2-instanssiin käyttäjätunnuksellasi Takkulan taulut ja katso jonkun takkulan tauluja käsitelleen SQL-kyselysi suoritussuunnitelma ja suoritusaika.
HELIA TIKO-05 11 ( 12) Liite 1 Oraclen PLAN_TABLE ja sen käyttö Oraclen optimoijan suoritussuunnitelman selvittämistä varten on käyttäjän luotava oma PLAN_TABLE taulu. Tämän luonti tehdään SQLPlus-ohjelmalla seuraavalla komennolla create table PLAN_TABLE ( statement_id varchar2(30), plan_id number, timestamp date, remarks varchar2(4000), operation varchar2(30), options varchar2(255), object_node varchar2(128), object_owner varchar2(30), object_name varchar2(30), object_alias varchar2(65), object_instance object_type varchar2(30), optimizer varchar2(255), search_columns number, id parent_id depth position cost cardinality bytes other_tag varchar2(255), partition_start varchar2(255), partition_stop varchar2(255), partition_id other long, distribution varchar2(30), cpu_cost io_cost temp_space access_predicates varchar2(4000), filter_predicates varchar2(4000), projection varchar2(4000), time qblock_name varchar2(30), other_xml clob );
HELIA TIKO-05 12 ( 12) Tämän jälkeen kirjoitetaan tutkittava SQL-komento lisäten sen alkuun teksti EXPLAIN PLAN FOR Suoritussuunnitelma kirjaantuu nyt PLAN_TABLE tauluun, mistä se saadaan tulostettua komennolla SELECT PLAN_TABLE_OUTPUT FROM TABLE(dbms_xplan.display('plan_table',null,'serial')); Huom. PLAN_TABLE:n säilyttää vanhat suoritussuunnitelmat ja sen sisältö kasvaa aina EXPLAIN PLAN komentoja suoritettaessa. Suoritussuunnitelmat voitaisiin identifioida käyttämällä komentomuotoa EXPLAIN PLAN SET STATEMENT_ID = '<identifier>' FOR mutta jos historiaa ei haluta säilyttää kannattaa PLAN_TABLE tyhjentää käytön jälkeen komennolla DELETE FROM PLAN_TABLE;