Olen tässä lueskellut The Definitive Guide To MongoDB -nimistä kirjaa (Kindle), joka on hyvä katsaus MongoDB-tietokannan toimintaperiaatteisiin. Huomasin, että MongoDB:ssä on paljon sellaisia ominaisuuksia, joita ei tule välttämättä ajatelleeksi sitä pintapuolisesti kokeillessa.
Vaikka MongoDB käyttää JSONin kaltaisia BSON-tietorakenteita dokumenttien kuvaamiseen, se erottelee tietotyypit paljon tarkemmin kuin JSON. Tyypillinen esimerkki tästä ovat päivämäärät, joita JSON ei määrittee lainkaan erilliseksi tyypikseen. MongoDB tallentaa ne siten, että JavaScript-shellissä käsiteltyinä päivämäärät ovat automaattisesti Date-objekteja.
64-bittisyys haasteena
MongoDB erottelee toisistaan erilaiset numeeriset tyypit, joita JavaScript käsittelee aina liukulukuina. BSONissa mahdollisia tyyppejä ovat tavu (byte), 32-bittinen kokonaisluku (int32), 64-bittinen kokonaisluku (int64) sekä 64-bittinen liukuluku (double). Näiden kanssa on syytä olla tarkkana, sillä JavaScript-shellissä toimiessa kaikki luvut ovat oletuksena liukulukuja.
MongoDB:hen siis tallentuu aina liukuluku, kun JavaScriptistä käsin syötetään dataa. Tämä voi aiheuttaa ongelmia lähinnä suurissa lukuarvoissa, sillä osaa 64-bittisistä kokonaisluvuista ei suoraan vastaa mikään 64-bittinen liukuluku. JavaScript ei pysty käsittelemään esimerkiksi lukua 9223372036854770001, vaan se muuttuu muotoon 9223372036854770000.
Ratkaisuna ongelmaan MongoDB:n JavaScript-shell tarjoaa NumberLong-tyypin, jota voi käyttää 64-bittisten kokonaislukujen tallentamiseen ja käsittelyyn. Esimerkiksi näin:
db.tests.insert({intval:NumberLong('9223372036854770001')})
{ "_id" : ObjectId("…"), "intval" : NumberLong("9223372036854770001") }
NumberLongia on käytettävä myös $inc-operaattorin kanssa, jotta lukuarvo säilyy kokonaislukuna:
db.tests.update({$inc:{intval:NumberLong('1')}})
{ "_id" : ObjectId("…"), "intval" : NumberLong("9223372036854770002") }
Silloin kun MongoDB:hen tallennettua dataa käsitellään esimerkiksi Python-sovelluksesta käsin pymongo-kirjastolla, datatyypit menevät automaattisesti oikein, sillä Python ymmärtää eron liukulukujen ja kokonaislukujen välillä. Tyypiksi tulee automaattisesti int64 (NumberLong), jos lukuarvo on yli 32-bittinen.
ObjectId
MongoDB-dokumenttien tunnisteena käytettävä ObjectId on oma mielenkiintoinen tietorakenteensa. Useimmissa muissa tietokannoissa ID-arvot ovat kokonaislukuja tai merkkijonoja, mutta MongoDB käsittelee niitä objekteina.
ObjectId koostuu 12 tavusta, jotka esitetään yleensä heksadesimaalissa muodossa ObjectId('4e71009ce0395a172f000001'). Tavuilla on seuraavanlainen merkitys:
0-3: Timestamp (4e71009c) = päivämäärä 2011-09-14 22:29:32
4-6: Machine ID (e0395a) = MD5('MacBookAir.local') kolme ens. tavua
7-8: Process ID (172f) = 5935 ttys002 0:00.28 python
9-11: Counter (000001) = laskuri
Näistä ehkä mielenkiintoisin on aikaleima, josta voidaan siis aina päätellä, milloin jokin MongoDB:hen tallennettu dokumentti on luotu. MongoDB-shellissä leiman voi tarkistaa näin:
ObjectId('4e71009ce0395a172f000001').getTimestamp()
ISODate("2011-09-14T19:29:32Z")
Eräs ObjectId-tunnisteiden tärkeimpiä ominaisuuksia on se, että niitä ei generoida palvelimella, vaan tietokanta-ajureissa. Toisin kuin esimerkiksi MySQL:n autoincrement-kentissä, sovellus tietää siis jo etukäteen, millä ID:llä dokumentti tullaan tallentamaan tietokantaan. Tämä tarkoittaa, että mitään LAST_INSERT_ID() -kikkailua ei tarvita relaatioiden luomiseen. Kaikki viittaukset dokumentien välille voidaan määritellä valmiiksi, ja sitten vain tallennetaan kaikki dokumentit kerralla.