Tapahtumien hallinta (TxCL)

Video on koostettu Toni Taipaluksen kevään 2023 luentonauhoitteista. Videolla mainitut viittaukset demoihin tai tenttiin eivät pidä paikkaansa keväällä 2024. Ajankohtaiset tiedot kurssin suorittamisesta löytyvät etusivulta.

Luku tekstinä

# luku-4.6

4.6 TxCL

SQL:n tapahtumanhallinta (Transaction Control Language, TxCL) sisältää muihin SQL:n osa-alueisiin verrattuna vähän komentoja, mutta teoria komentojen takana on runsasta jatkuvasti kehittyvää. Relaatiotietokantaa käyttävän sovellusohjelman tapahtumanhallinnan suunnittelu on erityisen tärkeää tietokantaohjelmoijalle. Toisaalta myös ns. NoSQL-tietokantaparadigmojen yleistyminen ja niihin liittyvä, relaatiotietokannoista poikkeava tapahtumanhallinta on erityisen hyödyllistä myös DBA:n (database administrator) ja konsultin tuntea.

Tietokantajärjestelmä on moniajoympäristö, jossa voidaan suorittaa jopa satoja tuhansia operaatioita sekunnissa. Jos kaikki operaatiot suoritettaisiin peräkkäin, DBMS toimisi liian hitaasti. Tästä syystä tietokantaan kohdistuvat käskyt on ryhmiteltävä tapahtumiksi (transaction) ja suoritettava samanaikaisesti eli rinnakkain. Tapahtumalla tarkoitetaan joukkoa (1..n) operaatioita. Operaatio on SQL-lause tai osa SQL-lauseesta, esim. SELECT- tai UPDATE-lause. Operaatiot jaetaan yleisellä tasolla luku- ja kirjoitusoperaatioihin. Lukuoperaatio lukee tietokannasta tietueen keskusmuistiin, ja kirjoitusoperaatio kirjoittaa muuttujan arvon tietokantaan.

4.6.1 Tapahtumanhallinta SQL:n näkökulmasta

Tapahtumia hallitaan SQL-standardin mukaisesti kolmella komennolla:

  • START [TRANSACTION] tai BEGIN [TRANSACTION] aloittaa tapahtuman.
  • COMMIT [WORK] vahvistaa kirjoitusoperaatiot tietokantaan ja lopettaa tapahtuman.
  • ROLLBACK [WORK] peruuttaa kaikki tapahtuman operaatioiden tekemät muutokset ja lopettaa tapahtuman.

Oletuksena vain vahvistetun (ts. COMMIT-käskyn saaneen) tapahtuman tekemät muutokset tietokantaan ovat muiden tapahtumien luettavissa.

SQL-standardi vaatii, että tapahtumanhallintaa voidaan käyttää kaikkiin DML-operaatioihin. Useat suositut relaatiotietokannanhallintajärjestelmät kuitenkin laajentavat tapahtumanhallintansa koskemaan myös DDL-operaatioita. Kokoava esimerkki tapahtumanhallinnan komennoista on esitetty alaluvussa 4.6.2.2.

4.6.2 ACID

Relaatiotietokannanhallintajärjestelmille on tyypillistä, että niiltä vaaditaan ja että ne täyttävät neljä perusvaatimusta:

  • Atomicity (jakamattomuus): tapahtuma on atominen. Joko kaikki tapahtuman sisältämät operaatiot onnistuvat tai ne kaikki epäonnistuvat. Toisin sanoen tapahtuman kaikki operaatiot suoritetaan onnistuneesti tai toimitaan niin kuin tapahtumaa ei olisi koskaan aloitettukaan.
  • Consistency (oikeellisuus): tapahtuma muuttaa tietokannan yhdestä oikeellisesta tilasta toiseen oikeelliseen tilaan. Tietokannan tilan sanotaan olevan oikeellinen, jos kaikki tietokannan sisältämä data noudattaa tietokannan sisäistä liiketoimintalogiikkaa.
  • Isolation (eristyvyys): jos usea tapahtuma suoritetaan samanaikaisesti, yhden tapahtuman tulokset ovat eroteltavissa toisen tapahtuman tuloksista. Toisin sanoen tietokanta on lopulta samassa tilassa riippumatta siitä, suoritetaanko tapahtumat rinnakkain vai peräkkäin.
  • Durability (pysyvyys): tapahtuman tulokset ovat pysyviä. Jos tapahtuma suoritetaan onnistuneesti, tulokset eivät katoa.

Näistä neljästä perusvaatimuksesta käytetään akronyymiä ACID. Seuraavassa on selitetty tarkemmin ACID-ominaisuuksia.

4.6.2.1 A: Jakamattomuus

Tietokantaan kohdistuvat operaatiot ryhmitellään tapahtumiksi. Yksi tapahtuma koostuu yhdestä tai useammasta operaatiosta. Operaatioiden onnistumista tai epäonnistumista mitataan tapahtumien näkökulmasta. Jos kaikki tapahtuman operaatiot onnistuvat, tapahtuma on suoritettu onnistuneesti eli vahvistettu (commit). Yhdenkin operaation epäonnistuessa tapahtuman sanotaan keskeytyneen (aborted) ja tällöin kaikki tapahtuman jo suoritetut operaatiot peruutetaan (rollback). Tapahtuman peruuttamisen mahdollistaa alaluvussa 2.1.2 esitelty tapahtumaloki. Operaation epäonnistumisen voi aiheuttaa esimerkiksi:

  • Käyttäjältä saatu keskeytys.
  • Tapahtuma havaitsee, että operaation suorittaminen rikkoisi tietokannan liiketoimintalogiikkaa.
  • Jokin käyttöjärjestelmästä (esim. I/O-operaatio) johtuva virhe.

Jakamattomuudella tarkoitetaan nimensä mukaisesti sitä, että tapahtuma on jakamaton: sen sisältämistä operaatioista suoritetaan onnistuneesti kaikki tai ei yhtäkään. Sen, miten operaatiot ryhmitellään tapahtumiin päättää tavallisesti tietokantaohjelmoija tai muu sovelluskehittäjä.

4.6.2.2 C: Oikeellisuus

Alla on esimerkki tapahtumasta, joka muuttaa tietokannan yhdestä oikeellisesta tilasta toiseen. Voitaisiin tulkita, että alla olevassa tapahtumassa on neljä operaatiota: SELECT (luku), UPDATE (luku ja kirjoitus) ja SELECT (luku). SQL-avainsanat on esitetty suuraakkosilla. Tapahtuma noudattaa kuvitteellisen pankin liiketoimintasääntöä, jonka mukaan tilillä täytyy olla vähintään 0 euroa rahaa.

BEGIN TRANSACTION nosta_rahaa(tilinro, nostomaara);
    SELECT saldo 
    FROM tili 
    WHERE tilinro = :tilinro;
    
    if (!tilinro):
        print("Tiliä ei löytynyt.");
        ROLLBACK;
    endif;
    
    UPDATE tili
    SET saldo = saldo - :nostomaara
    WHERE tilinro = :tilinro;
    
    SELECT saldo
    FROM tili
    WHERE tilinro = :tilinro;
    
    if (saldo < 0):
        print("Tilillä ei ole tarpeeksi rahaa.");
        ROLLBACK;
    endif;
    
    print("Nosto onnistui.");
    
    COMMIT;

END TRANSACTION nosta_rahaa;

SQL-käsky BEGIN TRANSACTION aloittaa tapahtuman. Ensimmäinen operaatio noutaa annetun parametrin mukaisen tilin saldon muistiin. Jos tilinumeroa ei löydy, koko tapahtuma peruutetaan komennolla ROLLBACK.

Seuraava SQL-lause vähentää muistiin luetusta saldosta nostomaara-parametrin arvon. Jos saldo on kirjoitusoperaation jälkeen negatiivinen, koko tapahtuma peruutetaan komennolla ROLLBACK. Muussa tapauksessa suoritetaan COMMIT-käsky, joka vahvistaa tapahtuman tulokset. COMMIT-käskyn jälkeen tulokset ovat myös muiden tapahtumien luettavissa.

On syytä huomata, että yllä olevan esimerkin tarkoitus on demonstroida erityisesti tapahtumanhallinnan komentoja. Saldon tarkastaminen voitaisiin toteuttaa myös tietokannanhallintajärjestelmän tasolla (kuten alaluvussa 4.4.3) tai isäntäkielen ohjelmakoodissa tietokannasta luetun saldon ja nostomäärän vertailuna.

4.6.2.3 I: Eristyvyys

Tietokannoille on tyypillistä, että moni käyttäjä käyttää tietokantaa samanaikaisesti, jolloin tapahtumia suoritetaan samanaikaisesti eli rinnakkain. Tarkastellaan seuraavaa yksinkertaista esimerkkiä kahdesta tapahtumasta, jotka käsittelevät samaa pankkitiliä:

  1. Tapahtuma T1 lukee pankkitilin saldon (100 euroa) muistiin.
  2. Tapahtuma T2 lukee pankkitilin saldon (100 euroa) muistiin.
  3. Tapahtuma T1 kasvattaa saldoa muistissa 20 eurolla ja kirjoittaa uuden saldon (120) tietokantaan.
  4. Tapahtuma T2 kasvattaa saldoa muistissa 20 eurolla ja kirjoittaa uuden saldon (120) tietokantaan.

Kun rinnakkaiset tapahtumat T1 ja T2 on onnistuneesti suoritettu, pankkitilillä on 120 euroa. Jos tapahtumat olisi suoritettu peräkkäin, pankkitilillä olisi 140 euroa ja täten yllä kuvattu rinnakkainen tapaus ei täytä eristyvyyden vaatimusta. Yllä kuvattua ongelmaa kutsutaan menetetyksi päivitykseksi (lost update). Tietokannanhallintajärjestelmien ratkaisuja rinnakkaisuuden hallintaan tarkastellaan alaluvuissa 4.6.3 ja 4.6.4.

4.6.2.4 D: Pysyvyys

ACID:n pysyvyydellä tarkoitetaan datan pysyvyyttä: kun tapahtuma on suoritettu onnistuneesti loppuun, data on kirjoitettu tietokantaan. Pysyvyyttä rikkovaa toimintaa voisi olla esimerkiksi COMMIT-operaation onnistumisen ilmoittaminen, vaikka kirjoitettava data on todellisuudessa vasta kiintolevyn sisäisessä välimuistissa. Toisin sanoen tapahtuman tekemät muutokset säilyvät, vaikka järjestelmä kaatuisi välittömästi tapahtuman onnistumisen jälkeen. Vakavien, tallennuslaitteiden laitevirheiden sietokykyä ei vaadita.

# moniacid

4.6.3 Rinnakkaisuudenhallinta

Ajoituksella (schedule) tarkoitetaan sitä järjestystä, jossa useamman rinnakkaisen tapahtuman operaatiot suoritetaan ja on olemassa useita vaihtoehtoisia ajoituksia, joista toiset täyttävät ACID:n eristyvyyden vaatimuksen ja toiset eivät. Kuten aiemmin mainittiin, tapahtumien suorittaminen peräkkäin (ns. sarjallinen (serial) ajoitus) on liian hidasta, ja käytännössä kaikkien RDBMS:n ajoitus on rinnakkaista eli ei-sarjallista (non-serial). Sarjallistuvaksi (serializable) ajoitukseksi kutsutaan sellaista rinnakkaista ajoitusta, joka on ekvivalentti eli yhtäpitävä jonkin sarjallisen ajoituksen kanssa.

Kuvio 4.8: Tapahtumien ajoitusten joukot. Rinnakkaisuus ei takaa sarjallistuvuutta.
Kuvio 4.8: Tapahtumien ajoitusten joukot. Rinnakkaisuus ei takaa sarjallistuvuutta.

Tapahtumanhallinta, joka täyttää ACID:n eristyvyyden (isolation) vaatimuksen sanotaan noudattavan sarjallistuvaa ajoitusta. Rinnakkaisuudenhallinta (concurrency control) määrittää, millä menetelmillä sarjallistuva ajoitus saavutetaan. Vaihtoehtoja ovat esimerkiksi tapahtumille annettavat aikaleimat (timestamp ordering), erityisesti hajautetuissa tietokannoissa käytetyt vahvistusajan mukaiset aikaleimat (commitment ordering) ja tällä kurssilla käsiteltävä lukitus (locking). Korkeammalla tasolla puhutaan optimistisesta (pyritään ehkäisemään ongelmatilanteet) ja pessimistisestä (pyritään ratkaisemaan ongelmatilanteet) rinnakkaisuudenhallinnasta.

Rinnakkaisista tapahtumista aiheutuvat ongelmatilanteet ratkaistaan tietokannanhallintajärjestelmissä monesti lukituksella. Lukituksella tarkoitetaan tietokannan tietueille sallittujen operaatioiden rajoittamista. Tietokannanhallintajärjestelmä pitää kirjaa tietueiden lukoista ns. lukkotaulussa. Lukitus on matalan tason tekniikka, johon tietokantaohjelmoija tai muukaan käyttäjä ei tavallisesti pääse suoraan vaikuttamaan. Tarkastellaan seuraavaksi yleistä relaatiotietokannanhallintajärjestelmien toteutustapaa lukitukselle.

4.6.3.1 Lukitustavat

Lukituksen rakeisuudelle on erilaisia toteutustapoja:

Binäärilukituksessa tietue on joko lukittu (1) tai lukitsematon (0). Kun tietue on lukittu binäärilukituksella, toinen tapahtuma ei voi käyttää sitä millään tavalla. Tietue voi olla taulun sarakkeen ja rivin leikkauskohta, taulun rivi, taulu, taulualue tai koko tietokanta riippuen tietokannanhallintajärjestelmän toteutustavasta.

Luku/kirjoituslukituksessa käytössä on luku- (S eli shared lock) ja kirjoituslukkoja (X eli exclusive lock). Kun tietue on lukulukittu, kaikki tapahtumat voivat lukea sitä ja saada siihen lukulukon, mutta eivät kirjoittaa siihen eivätkä saada siihen kirjoituslukkoa. Kun tietue on kirjoituslukittu, vain yksi tapahtuma voi muuttaa sitä, eikä yksikään toinen tapahtuma voi lukea sitä. Joissakin tapauksissa tapahtumalle voidaan sallia ns. lukon korotus, jolloin lukulukko muutetaan kirjoituslukoksi.

Nykyään relaatiotietokannanhallintajärjestelmissä on käytössä hienorakenteisemmat lukkotyypit. Tuotteesta riippuen erilaisia lukkotyyppejä on tavallisesti luku- ja kirjoituslukot mukaan laskettuna viidestä seitsemään. Erilaiset lukkotyypit eivät kuitenkaan takaa ACID-ominaisuuksien eristyvyysvaatimusta, vaan niiden lisäksi tarvitaan lukitusprotokolla, joka on tavallisesti kaksivaiheinen lukitus.

4.6.3.2 Kaksivaiheinen lukitus

Tarkastellaan seuraavaksi kaksivaiheista lukitusprotokollaa (two-phase locking, 2PL) luku/kirjoituslukituksella. Kaksivaiheisen lukitusprotokollan mukaan tapahtuma jaetaan kahteen osaan:

  1. Ensimmäisessä eli laajentamisvaiheessa tapahtuma varaa käyttöönsä tarvitsemansa lukot. Sellaisiin tietueisiin, joista ainoastaan luetaan, varataan lukulukko. Sellaisiin tietueisiin, joihin kirjoitetaan, varataan kirjoituslukko. Lukot varataan juuri ennen tietueeseen kohdistuvan operaation suorittamista. Yhtään lukkoa ei vapauteta. Jos käytössä on lukkojen korotus, kaikki korotukset tehdään tässä vaiheessa.
  2. Jälkimmäisessä eli supistamisvaiheessa lukot vapautetaan. Lukko vapautetaan heti, kun tietueeseen ei tarvitse enää tarvita lukkoa. Yhtään uutta lukkoa ei varata, eikä lukkoja koroteta.

Kaksivaiheisen lukituksen toteutukselle on yleisellä tasolla neljä erilaista toteutustapaa:

  • Yllä kuvattu protokolla (ns. perusprotokolla).
  • Konservatiivinen protokolla eroaa yllä kuvatusta perusprotokollasta siten, että kaikki tapahtuman tarvitsemat lukot varataan laajentamisvaiheessa ennen yhdenkään operaation suorittamista.
  • Tiukka (strict) protokolla (S2PL) eroaa yllä kuvatusta perusprotokollasta siten, että kaikki tapahtuman tarvitsemat kirjoituslukot vapautetaan supistamisvaiheessa vasta, kun tapahtuma on ohi.
  • Vahva ja tiukka (strong strict) protokolla (SS2PL) eroaa yllä kuvatusta perusprotokollasta siten, että kaikki tapahtuman tarvitsemat lukot vapautetaan supistamisvaiheessa vasta, kun tapahtuma on ohi.
Kuvio 4.9: Kaksivaiheiset lukitusprotokollat. Vaiheet on havainnollistettu pystyviivalla.
Kuvio 4.9: Kaksivaiheiset lukitusprotokollat. Vaiheet on havainnollistettu pystyviivalla.

4.6.3.3 Deadlock-tilanteet

Kaksivaiheisen lukituksen potentiaalisina ongelmina voidaan pitää ns. deadlock-tilannetta, jossa tapahtumat odottavat toistensa vapauttavan lukkoja, mutta aika ei ratkaise tilannetta. Tarkastellaan esimerkkitilannetta, jossa tietokantaan kohdistuvat samanaikaiset, kaksivaiheista lukitusta noudattavat tapahtumat T1 ja T2 saattavat tietokannanhallintajärjestelmän deadlock-tilaan:

T1 T2 Lopputulos
Kirjoituslukitus tietueeseen A. Tietue A kirjoituslukittu.
Kirjoituslukitus tietueeseen B. Tietue B kirjoituslukittu.
Kirjoituslukitus tietueeseen B. Odotetaan, että T2 vapauttaa lukon.
Kirjoituslukitus tietueeseen A. Odotetaan, että T1 vapauttaa lukon.

Vaikka ACID-ominaisuuksien eristyvyysvaatimusta voidaan kohdealueesta riippuen pitää tärkeämpänä kuin deadlock-tilanteiden välttämistä, myös deadlock-tilanteita voidaan käsitellä. On olemassa erilaisia lähestymistapoja deadlock-tilanteisiin:

  • Estetään deadlock-tilanteet protokollalla, eli annetaan jokaiselle tapahtumalle prioriteetti aikaleiman perusteella:
    • Wait-die-protokolla: vanhojen tapahtumien annetaan odottaa uudempien tapahtumien vapauttavan lukkoja. Tapahtumat peruutetaan (ROLLBACK), jos ne joutuvat odottamaan itseään vanhempia tapahtumia.
    • Wound-wait-protokolla: uusien tapahtumien annetaan odottaa vanhempien tapahtumien vapauttavan lukkoja. Tapahtumat peruutetaan, jos ne joutuvat odottamaan itseään uudempia tapahtumia.
  • Tunnistetaan deadlock-tilanteet ajasta, jonka tapahtuma on joutunut odottamaan ja peruutetaan tapahtuma määrätyn odotusajan jälkeen.
# moni2pl

4.6.4 Eristyvyystasot

SQL-standardi määrittää neljä eristyvyystasoa (isolation level). Eristyvyystasolla voidaan vaikuttaa rinnakkaisten tapahtumien nopeuteen ja potentiaalisiin ongelmiin: väljempi eristyvyystaso johtaa teoriassa nopeampiin tapahtumiin, mutta nostaa potentiaalisten ongelmien määrää. Vain vahvin eristyvyystaso on nimensä mukaisesti sarjallistuva. Eristyvyystaso voidaan määrittää tapahtuma- tai tietokantapalvelinkohtaisesti. Joissakin tuotteissa on mahdollista määrittää myös taulukohtaisia eristyvyystasoja.

Seuraavassa on lueteltu SQL-standardin mukaiset eristyvyystasot vahvimmasta väljimpään:

  1. Sarjallistuva (SERIALIZABLE): vahvin eristyvyystaso, jonka seurauksena ovat hitaimmat tapahtumat. tapahtuma pitää kaikki käsittelemänsä tietueet lukittuina, kunnes tapahtuma on ohi. Lisäksi lukitaan tapahtuman käsittelemä arvoväli.
  2. Toistettavat lukuoperaatiot (REPEATABLE READ): tapahtuman käsittelemät tietueet ovat lukittuina, kunnes tapahtuma on ohi. Arvoväliä ei lukita.
  3. Vahvistettujen tietueiden luku (READ COMMITTED): vaikka tapahtuma T1 olisi lukenut muuttujan arvon muistiin, toinen tapahtuma T2 voi muokata tätä arvoa ennen kuin tapahtuma T1 on ohi.
  4. Vahvistamattomien tietueiden luku (READ UNCOMMITTED): eristyvyystasolla toimiva tapahtuma voi lukea muiden tapahtumien tekemät, vahvistamattomatkin muutokset. Vahvistamaton muutos tarkoittaa sellaista operaatiota, jonka tapahtuma ei ole saanut COMMIT- tai ROLLBACK-käskyä.
Kuvio 4.10: Rinnakkaisuudenhallinnan osa-alueita ja toteutuksia.
Kuvio 4.10: Rinnakkaisuudenhallinnan osa-alueita ja toteutuksia.

4.6.4.1 Ongelmatilanteet

Tarkastellaan seuraavaksi tarkemmin tunnistettuja ongelmia eli poikkeamia (anomaly), jotka rikkovat eristyvyyden perusvaatimusta.

4.6.4.1.1 Haamuluku (phantom read)

Saman tapahtuman samanlaiset lukuoperaatiot palauttavat erilaisen joukon rivejä. Ongelman saa aikaan jokin muu, samanaikainen tapahtuma, joka lisää tauluun rivin tai rivejä käsiteltävälle arvovälille. Kysymysmerkki on SQL-standardin mukainen merkintä sidotulle parametrille (bound parameter). Sidotun parametrin arvo saadaan sovellusohjelmalta ajon aikana.

T1 T2
BEGIN;
SELECT tilinro
FROM tili
WHERE saldo > 1000000;
BEGIN;
INSERT INTO tili (tilinro, saldo)
VALUES (?, 2000000);
COMMIT;
SELECT tilinro
FROM tili
WHERE saldo > 1000000;

Nyt tapahtuman T1 kaksi lukuoperaatiota palauttavat erilaisen joukon rivejä, mikä rikkoo sarjallistuvuuden perusperiaatetta. Mikäli koko käsiteltävä arvoväli (saldo > 1000000) lukittaisiin tapahtuman T1 ajaksi, ei yllä kuvattua ongelmaa voisi esiintyä.

Syntaksilla :muuttuja merkitään SQL:ssä ns. dynaamisia parametreja. Parametrien arvot tulevat isäntäkielestä järjestelmän ajon aikana.

4.6.4.1.2 Ei-toistettavat lukuoperaatiot (non-repeatable reads)

Tapahtuman T1 yhden rivin palauttava lukuoperaatio R1 palauttaa erilaisen rivin kuin saman tapahtuman samanlainen lukuoperaatio R2. Ongelman saa aikaan toinen, samanaikainen tapahtuma T2, joka päivittää riviä.

T1 T2
BEGIN;
SELECT saldo
FROM tili
WHERE tilinro = ?;
BEGIN;
UPDATE tili
SET saldo = saldo + 100
WHERE tilinro = ?;
COMMIT;
SELECT saldo
FROM tili
WHERE tilinro = ?;

Nyt tapahtuman T1 kaksi lukuoperaatiota palauttavat erilaisen rivin. Mikäli käsiteltävä rivi (tilinro = :a) pidettäisiin lukittuna koko tapahtuman T1 ajan, ei yllä kuvattua ongelmatilannetta voisi tapahtua.

4.6.4.1.3 Likainen lukuoperaatio (dirty read)

Tapahtuma T2 lukee taulusta sellaisen rivin, jota toisen tapahtuman T1 operaatio on päivittänyt, mutta tapahtumaa T1 johon kirjoitusoperaatio sisältyy, ei ole vahvistettu (commit). Ongelma likaisessa luvussa on mahdollisuus, että päivitystä ei koskaan suoriteta onnistuneesti loppuun, vaan se peruutetaan (rollback).

T1 T2
BEGIN;
BEGIN;
UPDATE tili
SET saldo = saldo + 100
WHERE tilinro = ?;
SELECT saldo
FROM tili
WHERE tilinro = ?;
ROLLBACK;

Nyt tapahtumat T1 päivittää tilin saldoa, mutta kirjoitusoperaatio peruutetaan. Ennen kirjoitusoperaation peruuttamista tapahtuma T2 on ehtinyt lukea tilin uuden saldon. Jos vahvistamattomien tietueiden lukua ei sallittaisi, yllä kuvattua ongelmatilannetta ei voisi tapahtua.

Alla on esitetty taulukko standardin mukaisilla eristyvyystasoilla esiintyvistä potentiaalisista ongelmista.

Haamuluvut Ei-toistettavat luvut Likaiset luvut
Sarjallistuva ei ei ei
Toistettavat lukuoperaatiot kyllä ei ei
Vahvistettujen tietueiden luku kyllä kyllä ei
Vahvistamattomien tietueiden luku kyllä kyllä kyllä

# monirinn

These are the current permissions for this document; please modify if needed. You can always modify these permissions from the manage page.