- Praktický úvod do MongoDB (1): NoSQL opravdu snadno
- Praktický úvod do MongoDB (2): Indexy a agregace
- Praktický úvod do MongoDB (3): clustering
MongoDB je moderní a nejoblíbenější NoSQL databáze. V tomto dvoudílném seriálu ale teorii řešit nebudeme (úvod do NoSQL najdete na v tomto článku) – jednoduše si to vyzkoušíme!
Instalujeme
Instalace v Ubuntu 14.04 server je velmi jednoduchá:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 sudo echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list sudo apt-get update && sudo apt-get install -y mongodb-org
Pro základní seznámení s MongoDB použijeme příkazovou řádku – pro praktická nasazení existují knihovny pro většinu programovacích jazyků, například Python (k tomu v druhém díle). Vyzkoušejme si to / jednoduše napište slovo “mongo”:
root@ubuntu:~# mongo MongoDB shell version: 3.0.6 connecting to: test Welcome to the MongoDB shell. For interactive help, type "help". For more comprehensive documentation, see http://docs.mongodb.org/ Questions? Try the support group http://groups.google.com/group/mongodb-user Server has startup warnings: 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] ** We suggest setting it to 'never' 2015-08-25T03:19:46.323-0700 I CONTROL [initandlisten] > > show dbs local 0.078GB >
Používáme
MongoDB se neovládá SQL příkazy jako tradiční relační databáze a navíc neobsahuje tabulky, ale kolekce “dokumentů”. Těmi jsou JSON (resp. na disku uložené jako BSON) struktury a nemají žádný dopředu daný fixní formát, nemusíte tedy vytvářet tabulky a říkat jaká políčka mají mít (tedy MongoDB je schema-less). Každý záznam může mít klidně jiné položky. JSON je pro zpracování v moderních programovacích jazycích nesmírně příjemná záležitost a celá struktura je lidsky dobře čitelná. To co byste obvykle v relačním světě řešili soustavou tabulek budete v MongoDB dávat do jednoho dokumentu – například pro zákazníka. Jaký má JSON formát? Znaky {} ohraničují objekty, klíč (řekněme název “políčka”) se odděluje dvojtečkou a “políčka” čárkou. Takže například:
{"jmeno":"martin", "vek":30}
Objekty se mohou vnořovat a [] definuje pole. Budeme pracovat s něčím takovýmto:
{ "jmeno":"tomas", "udaje":{"vek":30,"pohlavi":"muz"}, "objednavky":[ {"faktura":1,"polozky":[ {"produkt":"kartacek", "pocet":4}, {"produkt":"ubrousky", "pocet":3} ]}, {"faktura":2,"polozky":[ {"produkt":"nocnik", "pocet":1} ]}, ] }
Myslím, že není potřeba detailně vysvětlovat – je to dokument pro člověka, o kterém vedeme nějaké údaje, evidujeme objednávky a v nich položky. Budeme pracovat ještě s dalšími třemi lidmi – všimněte si, že struktura je velmi flexibilní, takže například u jednoho záznamu přidáme nový údaj – to v relační databázi není tak jednoduché, ale v MongoDB to je triviální.
{ "jmeno":"martin", "udaje":{"vek":25,"pohlavi":"muz"}, "objednavky":[ {"faktura":1,"polozky":[ {"produkt":"nocnik", "pocet":1}, {"produkt":"ubrousky", "pocet":3} ]} ] } { "jmeno":"alenka", "udaje":{"vek":35,"pohlavi":"zena","velikost":"S"}, "objednavky":[ {"faktura":1,"polozky":[ {"produkt":"tricko", "pocet":2}, {"produkt":"ubrousky", "pocet":1} ]} ] }
Vložíme teď do výchozí databáze (local – pokud si chcete udělat jinou, je to snadné – napište “use mojedb”) tyto naše záznamy. Použijte příkaz db. následovaný názvem vaší kolekce dokumentů, v našem případě použijeme “lide” a příkaz insert. Udělejte závorku a zmáčkněte enter, překopírujte náš JSON a ukončete závorkou. To je vše – data jsou v databázi.
> db.lide.insert( ... { ... "jmeno":"tomas", ... "udaje":{"vek":30,"pohlavi":"muz"}, ... "objednavky":[ ... {"faktura":1,"polozky":[ ... {"produkt":"kartacek", "pocet":4}, ... {"produkt":"ubrousky", "pocet":3} ... ]}, ... {"faktura":2,"polozky":[ ... {"produkt":"nocnik", "pocet":1} ... ]}, ... ] ... } ... ) WriteResult({ "nInserted" : 1 }) > db.lide.insert( ... { ... "jmeno":"martin", ... "udaje":{"vek":25,"pohlavi":"muz"}, ... "objednavky":[ ... {"faktura":1,"polozky":[ ... {"produkt":"nocnik", "pocet":1}, ... {"produkt":"ubrousky", "pocet":3} ... ]} ... ] ... } ... ) WriteResult({ "nInserted" : 1 }) > db.lide.insert( ... { ... "jmeno":"alenka", ... "udaje":{"vek":35,"pohlavi":"zena","velikost":"S"}, ... "objednavky":[ ... {"faktura":1,"polozky":[ ... {"produkt":"tricko", "pocet":2}, ... {"produkt":"ubrousky", "pocet":1} ... ]} ... ] ... } ... ) WriteResult({ "nInserted" : 1 })
Vyhledáváme data
Jaké tedy má naše firma zákazníky?
> db.lide.find() { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] } { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
Jak vidíte každému dokumentu bylo přiřazeno ObjectId – vyhledávání podle ObjectId je velmi rychlé, nicméně můžete vyhledávat i podle jiných políček.
Co víme o Martinovi?
> db.lide.find({"jmeno":"martin"} ) { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] }
Jaké známe muže?
> db.lide.find({"udaje.pohlavi":"muz"}) { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] }
Kdo si koupil tričko?
> db.lide.find({"objednavky.polozky.produkt":"tricko"}) { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
Najdi Tomáše a všechny ženy (použití OR dotazu)
> db.lide.find({$or : [{"jmeno":"tomas"},{"udaje.pohlavi":"zena"}]}) { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
Kdo je starší než 29 let?
> db.lide.find({"udaje.vek":{$gt:29}}) { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
U koho známe velikost oblečení?
> db.lide.find({"udaje.velikost":{$exists : true}}) { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
Koho jméno začíná na písmeno “t”? Použijeme REGEX:
> db.lide.find({"jmeno":{$regex : ".t"}}) { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] }
Zajímá vás jen informace o jménu a věku, vše ostatní teď nepotřebujete? Napište za první JSON objekt s filtrem druhý objekt s výpisem struktur, které chcete ve výsledku (1 znamená chci, 0 znamená nechci0.
> db.lide.find({},{jmeno:1,"udaje.vek":1,_id:0}) { "jmeno" : "tomas", "udaje" : { "vek" : 30 } } { "jmeno" : "martin", "udaje" : { "vek" : 25 } } { "jmeno" : "alenka", "udaje" : { "vek" : 35 } }
Potřebujete seřadit zákazníky podle věku vzestupně nebo sestupně?
> db.lide.find().sort({"udaje.vek":1}) { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] } { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] } > db.lide.find().sort({"udaje.vek":-1}) { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "S" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "jmeno" : "tomas", "udaje" : { "vek" : 30, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "kartacek", "pocet" : 4 }, { "produkt" : "ubrousky", "pocet" : 3 } ] }, { "faktura" : 2, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 } ] } ] } { "_id" : ObjectId("55dc488bbdd068b804752b9e"), "jmeno" : "martin", "udaje" : { "vek" : 25, "pohlavi" : "muz" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "nocnik", "pocet" : 1 }, { "produkt" : "ubrousky", "pocet" : 3 } ] } ] }
Alenka nám bohužel trochu přibrala a potřebujeme změnit její záznam o velikosti. Použijeme tedy funkci update a nejprve zadáme filtrační pravidlo (tam si vybereme Alenku) a následně změníme příslušný údaj.
> db.lide.update({"jmeno":"alenka"},{$set : {"udaje.velikost":"L"}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > db.lide.find({"jmeno":"alenka"},{"udaje.velikost":1}) { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "udaje" : { "velikost" : "L" } }
Konečně se nám podařilo zjistit, jakou velikost má Tomáš. Tento údaj jsme dříve neměli a nenechali jsme si na něj žádný prostor (třeba “políčko” s hodnotou NULL). To ale vůbec nevadí – používáme přece schema-less NoSQL databázi:
> db.lide.update({"jmeno":"tomas"},{$set : {"udaje.velikost":"XL"}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > db.lide.find({"jmeno":"tomas"},{"udaje.velikost":1}) { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "udaje" : { "velikost" : "XL" } }
Co kdybychom se závěrem zbavili všech mužů?
> db.lide.find({"jmeno":"tomas"},{"udaje.velikost":1}) { "_id" : ObjectId("55dc441f5e7447845e2d13d4"), "udaje" : { "velikost" : "XL" } } > db.lide.remove({"udaje.pohlavi":"muz"}) WriteResult({ "nRemoved" : 2 }) > db.lide.find() { "_id" : ObjectId("55dc48a4bdd068b804752b9f"), "jmeno" : "alenka", "udaje" : { "vek" : 35, "pohlavi" : "zena", "velikost" : "L" }, "objednavky" : [ { "faktura" : 1, "polozky" : [ { "produkt" : "tricko", "pocet" : 2 }, { "produkt" : "ubrousky", "pocet" : 1 } ] } ] }
Ve výchozím stavu je MongoDB optimalizována na rychlé vyhledávání podle ObjectId – pokud si například vaše aplikace vytváří záznamy a ObjectId ví (třeba si ho uloží v cookie klienta nebo si drží v paměti třeba v REDIS key-value in-memory store, o kterém na cloudsvet.cz ještě uslyšíte), může k ni přímo přistupovat. Vyhledávání ostatních polí je pomalejší, MongoDB si musí všechny dokumenty projít – nicméně vaše aplikace to dělat nemusí (tzn. nemusí si k sobě stáhnout celou databázi), udělá to engine, takže to dává velký smysl. Navíc pokud podle některého z údaje vyhledáváte často, můžete nad ním vytvořit index – MongoDB se k němu pak chová tak, že podle něj rychle najde požadovaný dokument.
V příštím díle naznačíme nějaké velmi zajímavé pokročilejší věci: