Apprendre MongoDB

Synthèse des commandes essentielles que vous devez connaître

Quentin Busuttil

Lorsque, comme beaucoup, on vient du monde des bases de données relationnelles et de la syntaxe SQL, l'apprentissage et l'usage du noSQL est au début un peu déroutant. Voici donc un condensé des commandes les plus couramment utilisées (et utiles) dans MongoDB.

Commentez

- Titre : Apprendre MongoDB

- Auteur : Quentin Busuttil

- Parution : 30 décembre 2016

- Mise à jour : 16 mai 2018

- Licence : Le contenu de cet article est rédigé par Quentin Busuttil et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Notions propres à Mongo

- II. Installation

- III. Utilisation courante

- III-A. Utilisation des dates

- IV. Administration

- IV-A. Sécurité

- IV-A-1. Gestion des utilisateurs

- IV-A-2. Connexion

- IV-A-3. Chiffrement

- IV-B. Sauvegarde et restauration

- IV-C. Gestion des index

- IV-D. Données et statistiques

- V. Note de la rédaction de Developpez.com

I - Notions propres à Mongo

On parle de NoSQL (qui veut dire Not only SQL et non ?SQL), quelles sont donc les particularités de Mongo qui en font une base NoSQL ?

Il faut tout d’abord savoir que Mongo stocke ses données en BSON. Les données sont structurées exactement comme en JSON, mais il s’agit là d’une version binaire du JSON.

Un document BSON étant similaire à un document JSON, vous en avez très certainement déjà vu… Il s’agit de ceci.

1. {

2. "_id": "558d0b3c5fa02e7e218b4b0c",

3. "description": "James Bond 007 is on the search for a Russian decoding machine, known as Lektor. Bond needs to find this machine…",

4. "actors": "Sean Connery, Daniela Bianchi, Pedro Armendáriz, Lotte Lenya",

5. "director": "Terence Young",

6. "genre": "Action, Adventure, Thriller",

7. "imdbRating": "7.5",

8. "trailer": "https://www.youtube.com/watch?v=D-BvAV6jYgU",

9. "writers": "Richard Maibaum (screenplay), Johanna Harwood (adaptation)",

10. "year": "1962-12-31T23:00:00.000Z",

11. "likes":

12. {

13. "count": 29,

14. "likedByUser": true

15. }

16. }

Comme vous pouvez le constater, on a donc des clefs et des valeurs et, tout comme en JSON (car c’est du JSON), une valeur peut être un objet, un tableau, un tableau d’objets, etc., ce sont des sous-documents. Un document similaire à un objet JavaScript peut en contenir d’autres.

Par ailleurs, les documents ne sont pas rigides. Ainsi, un document peut contenir un champ tandis que le suivant ne contient pas ce dernier. Par exemple, dans le cas des utilisateurs d’un site, on pourrait connaître l’adresse de certains tandis qu’on l’ignore pour d’autres. Dans ce cas, contrairement au SQL où le champ serait vide, ici on peut tout simplement l’omettre (ce qui n’empêchera pas de l’ajouter par la suite).

Maintenant que vous savez ce qu’est un document, reste à comprendre la notion de collection. Rien de sorcier et, vous l’aurez peut-être déjà deviné, une collection est un groupe de documents, similaire à une table dans le monde du SQL. Vous aurez ainsi une collection regroupant les utilisateurs, une autre regroupant les articles, etc.

1. {

2. "_id": "558d0b395fa02e7e218b458c",

3. "firstname": "Morgan",

4. "lastname": "Haraki",

5. "mutualFriends": 2

6. },

7. {

8. "_id": "558d0b395fa02e7e218b4586",

9. "firstname": "Fred",

10. "lastname": "Farrid",

11. "mutualFriends": 0

12. },

13. {

14. "_id": "558d0b395fa02e7e218b4593",

15. "firstname": "Jérôme",

16. "lastname": "Lecomte",

17. "mutualFriends": 23

18. }

II - Installation

Mongo tourne sur un grand nombre d’OS et d’architectures. Il n’y a rien ici de très complexe dans son installation.

Sur les systèmes Debian like, généralement un petit coup d'apt install MongoDB suffira. Vous pouvez néanmoins utiliser les dépôts alternatifs proposés par Mongo afin d’avoir une version plus récente que celle proposée par votre OS.

Pour le Mac, homebrew permet l’installation également. Pour les binaires, Windows et toutes les autres options, le plus simple est encore d’aller directement consulter la page de téléchargement officielle.

III - Utilisation courante

Avant de pouvoir exécuter la moindre commande, il faut que la base de données soit lancée, cela se passe avec mongod (le d de dæmon) et se connecter au shell mongoDB, cela se passe avec mongo (le client).

Pour lancer la base de données, rien de très complexe, on lance la commande mongod et on lui passe quelques options si nécessaire.

1. # si la base de données n'est pas dans /data/db on doit spécifier le chemin avec --dbpath

2. mongod --dbpath db/

4. # si la base doit écouter sur un port spécifique

5. mongod --port 42

Enfin, on connecte le client Mongo et on pourra saisir toutes nos commandes. Rien de bien complexe, comme avec MySQL, on demande à mongo de se connecter à la base. Dans sa forme la plus simple, il s'agit juste d'exécuter mongo, mais il existe de nombreuses options. Nous en verrons certaines dans la partie administration.

1. # afficher toutes les bases de données

2. show dbs

4. # utiliser la base "madatabase"

5. use madatabase

7. # voir dans quelle base on est

8. db

10. # afficher les collections

11. show collections

13. # renommer une collection

14. db.oldname.renameCollection("newname")

16. # effacer une collection

17. db.contacts.drop()

19. # effacer la base dans laquelle on est

20. db.dropDatabase()

22. # insertion

23. db.contacts.insert({ first: 'Quentin', last: 'Busuttil' })

25. # sélection

26. db.contacts.find()

27. db.contacts.find({ first: 'quentin' })

28. db.contacts.find({ first: 'quentin', last: 'busuttil' })

30. # sélectionner les documents dont un array contient une valeur x

31. db.contacts.find({ tags: 'business' })

33. # ou un élément parmi plusieurs

34. db.contacts.find({ tags: { $in: ['business', 'french'] } })

36. # ou la combinaison de plusieurs

37. db.contacts.find({ tags: { $all: ['business', 'french'] } })

39. # sous cette forme l'array correspond exactement à cela (ordre des éléments compris)

40. db.contacts.find({ tags: ['business', 'french'] })

42. # requêter selon des propriétés d'un sous-document

43. # admettons l'objet suivant :

44. # { nom: "Quentin", agenda: { lundi: "programmation", mardi: "dev", mercredi: "code" } }

45. # on pourra matcher depuis son sous-document ainsi (notez bien les guillemets autour de agenda.mardi)

46. db.users.find({ 'agenda.mardi': 'dev' })

48. # requêter selon un match exact de sous-document

49. db.users.find({ agenda: { lundi: "programmation", mardi: "dev", mercredi: "code" } })

51. # OR sélectionner selon une condition ou une autre

52. # il faut utiliser $or avec un array dont chaque élément est un objet littéral de sélection

53. db.users.find({ $or: [{ _id:ObjectId("558d0b395fa02e7e218b4587") }, { _id:ObjectId("558d0b395fa02e7e218b4574") }] })

55. # récupérer seulement certains champs d'un document

56. db.users.find({}, { _id: 0, fild1: 1, fild2: 0 })

58. # type == food || snacks

59. db.inventory.find({ type: { $in: ['food', 'snacks'] } })

61. # sélectionner les documents comportant un champ particulier

62. db.users.find({ birthyear: { $exists: true } })

64. # sélectionner les documents dont la valeur est différente de

65. db.users.find({ birthyear: { $ne: 2000 } })

67. # ne récupérer que le sous-document (récup le sous-document d'id x dans le doc d'id z)

68. db.users.find({ _id: z, "contacts._id": x }, { "contacts.$._id" : 1 })

70. # ordonner la sélection

71. # 1 = croissant/alphabétique et -1 = décroissant/alphabétique inversé

72. db.users.find().sort({ addedOn: 1 })

75. # update

76. # on peut utiliser { multi: true } pour modifier plusieurs documents à la fois

77. # l'option { upsert: true } permet de créer le document s'il n'existe pas

78. db.users.update({ _id: x }, { $set: { "firstname": "Baboo" } })

80. # update en remplaçant le document par un nouvel objet

81. db.users.update({ _id: 'azert' }, obj)

83. # update un sous-document d'id y du doc d'id x (dans un tableau de sous-documents appelé contacts),

84. # on remplace le sous-doc par un autre objet

85. # le document à modifier ressemble ici à

86. #{

87. #  _id: x,

88. #  name: azerty,

89. #  contacts: [

90. #    {

91. #      _id: y,

92. #      name: 'qwerty',

93. #      email: 'qwerty@heymail.fr'

94. #    }

95. #  ]

96. #}

97. db.users.update({ _id: x, "contacts._id": y }, { $set: { "contacts.$":myNewDocument } })

99. # effacer un champ

100. db.users.update({ _id: x }, { $unset: { contacts: "" } })

102. # renommer un champ

103. db.users.update({}, { $rename: { "ancien_nom": "nouveau_nom" } }, { multi: true })

105. # effacer un champ de tous les documents d'une collection

106. # correspond à ALTER TABLE users DROP COLUMN zorglub

107. db.users.update({}, { $unset: { zorglub: "" } }, { multi: true } )

109. # retirer un élément d'un tableau

110. # (on retire l'ami "yyy" du tableau "friends" de l'utilisateur "xxx")

111. db.users.update({ _id: xxx }, { $pull: { friends: yyy } })

113. # on fait la même chose, mais on retire cet ami de tous les utilisateurs

114. # par exemple, l'ami yyy, s'est désinscrit

115. db.users.update({ friends: yyy }, { $pull: { friends: yyy } }, { multi: true })

117. # effacer un document

118. db.contacts.remove({ _id: ObjectId("55accc6c039c97c5db42f192") })

120. # effacer tous les documents d'une collection

121. db.contacts.remove({})

123. # effacer un sous-document d'id y du doc d'id x (dans un tableau de sous-documents)

124. db.users.update({ _id: x }, { $pull: { contacts: { _id: y } } })

126. # dans le même genre $pop permet d'enlever le dernier ou le premier élément d'un tableau

127. # respectivement en lui passant les valeurs 1 et -1

128. db.users.update({ _id: x }, { $pop: { contacts: -1 } })

Il est assez lourd de devoir taper à chaque fois db.users.commande, la console Mongo est aussi un shell JavaScript, il est donc tout à fait possible d'enregistrer des choses dans des variables !

1. // on enregistre la collection dans une variable

2. t = db.users

4. // nous pouvons maintenant faire comme ceci

5. t.find().pretty()

Elle est pas belle la vie ?

De la même manière, pour mettre à jour un objet, détaillons un peu ce que nous faisons ici db.users.update({_id: 'azert'}, obj) :

1. // on récupère un objet

2. obj = t.find({ _id: 'azert' });

4. // on le met à jour comme n'importe quel objet js

5. obj.city = "Lyon"

7. // et on en enregistre les modifs

8. db.users.update({ _id: 'azert' }, obj)

III-A - Utilisation des dates

JS oblige, les dates sont un peu à part entière. Dans la mesure où nous avons souvent besoin de les traiter, faisons rapidement le tour du propriétaire !

Lorsque l'on veut requêter une date dans Mongo, il faut créer un objet de type Date. Cela se fait soit directement via le constructeur Date de JavaScript, soit via le wrapper Mongo ISODate.

La fonction Date() appelée sans new retourne simplement la date courante en tant que chaîne de caractères. Les autres formes, c'est-à-dire new Date(xxx), new ISODate(xxx) et ISODate(xxx) sont équivalentes et font toutes appel au wrapper ISODate. Il ne s'agit donc que d'une question de préférence personnelle.

Maintenant que nous avons levé l'ambiguïté qu'il peut y avoir entre ces différentes méthodes, utilisons-les. Pour l'exemple, nous allons récupérer les utilisateurs créés depuis le 15 septembre 2015.

db.users.find({ signupDate: { $gte: ISODate("2015-09-15") } })

Évidemment, qui dit date, dit aussi heure. Franchissons le cap de la précision, les utilisateurs qui se sont enregistrés après le 15 septembre 2015 à 13h20.

db.users.find({ signupDate: { $gte: ISODate("2015-09-15T13:20:00.000Z") } })

Vous vous apercevez certainement que la précision va jusqu'à la milliseconde… En effet, le moteur de Mongo stocke et traite les dates en nombre de millisecondes depuis l'époque Unix (sous la forme d'un entier de 64 bits, si ça vous intéresse). Nous héritons donc de la précision de ce format.

Vous notez peut-être également que deux lettres viennent s'ajouter à notre équation. Le T vient simplement faire office de séparateur et indique Time, tandis que le Z signifie que nous traitons du temps en Zulu Time [en], autrement dit, l'heure UTC.

La présence ou l'absence du Z est importante. Mongo stocke dans tous les cas la date au format UTC. Cependant, si vous omettez le Z, Mongo considère qu'il s'agit du fuseau horaire courant, donc UTC+1 si vous êtes sur le fuseau horaire de Paris, ou plus généralement CET (Central European Time), autrement dit l'heure d'Europe centrale.

Vous le comprenez, en l'absence du Z, Mongo fera l'opération pour ajouter ou retrancher des heures à UTC avant de la stocker, tandis que si vous le précisez, il stocke tel quel.

IV - Administration

IV-A - Sécurité

IV-A-1 - Gestion des utilisateurs

Par défaut, Mongo est accessible sans authentification et permet toutes les actions sur la base. Il est évident dès lors que votre base est sur un serveur connecté sur Internet et que vous passez en production, cette configuration par défaut manque de sécurité. Ça devient même clairement du suicide si vous permettez de vous connecter à la base depuis le réseau… Réglage à effectuer dans /etc/mongod.conf :

1. # network interfaces

2. net:

3. port: 27017

4. # n'autorise que l'ip localhost (réglage par défaut)

5. bindIp: 127.0.0.1

De manière similaire à MySQL, vous ajoutez des utilisateurs et leur attribuez des droits (ici des rôles, sur diverses bases). Nous allons pour l'exemple créer un utilisateur « buzut » qui peut lire et écrire dans la base « blog_buzut » :

1. # on se place dans la base de données "buzut_blog"

2. use buzut_blog

3. db.createUser(

4. {

5. user: "buzut",

6. pwd: "azertyytreza",

7. roles: [

8. { role: "readWrite", db: "buzut_blog" },

10. # on peut ajouter d'autres rôles sur d'autres bases

11. { role: "read", db: "buzut_shop" }

12. ]

13. }

14. )

Une fois vos utilisateurs créés, il faut que vous configuriez mongo pour qu'il demande les accès. Pour cela, dans le fichier de config – situé dans /etc/mongod.conf – il faut ajouter le paramètre de sécurité correspondant :

1. # security est normalement commenté, il faut le décommenter

2. # ou l'ajouter s'il n'existe pas

3. # et ajouter la ligne suivante

4. security:

5. authorization: enabled

7. # vous pouvez aussi démarrer mongo avec le mode auth directement depuis la ligne de commande

8. mongod --auth

Si vous avez modifié le fichier de config, il faut que vous redémarriez mongo pour que les changements s'appliquent. Pour cela, faites sudo service mongod restart.

Attention, si vous créez un utilisateur non administrateur et que vous activez ensuite l'authentification, vous ne pourrez plus créer d'autres utilisateurs. Pour créer un utilisateur avec les droits administrateur :

1. db.createUser(

2. {

3. user: "admin",

4. pwd: "azertyytreza",

5. roles: [

6. { role: "dbOwner", db: "blog_buzut" }

7. ]

8. }

9. )

Bien pratique, vous trouverez la liste des rôles dans la doc et vous pouvez même créer des rôles sur mesure pour plus de souplesse.

Les utilisateurs sont stockés dans admin.system.users, vous pouvez donc lister l'ensemble des utilisateurs en affichant cette collection ou en utilisant la commande show users.

Vous pouvez changer le mot de passe d'un utilisateur avec db.changeUserPassword("username", "newPass") et effacer un utilisateur avec db.dropUser("username"). Ces deux commandes nécessitent les privilèges administrateur et elles doivent être exécutées depuis la base à laquelle les utilisateurs sont liés.

Enfin, il est également possible d'ajouter et révoquer des droits ultérieurement. Nous utiliserons pour cela les commandes db.grantRolesToUser et db.revokeRolesFromUser :

1. # il faut être placé dans la bonne base…

2. # vous commencez à avoir l'habitude

4. # pour ajouter des droits

5. db.grantRolesToUser(

6. "buzut",

7. [

8. { role: "readWrite", db: "logs" }

9. ]

10. )

12. # et pour enlever des droits

13. db.revokeRolesFromUser(

14. "buzut",

15. [

16. { role: "readWrite", db: "logs" }

17. ]

18. )

IV-A-2 - Connexion

Une fois les utilisateurs créés et la base paramétrée pour passer en mode « authorization », il faudra que vous vous connectiez avec vos identifiants, sinon, vous serez jetés ! Pour ce faire, il faut préciser le login et le mot de passe ainsi que la base à laquelle l'utilisateur est rattaché :

1. mongo -u buzut -p azertyyterza --authenticationDatabase blog_buzut

3. # il est aussi possible de se logger après la connexion

4. mongo

5. use blog_buzut

6. db.auth("buzut", "azertyyterza")

IV-A-3 - Chiffrement

Je ne parlerai pas ici de chiffrer la base en elle-même – cette fonction n'est disponible que pour WiredTigger dans la version entreprise de Mongo. Sans avoir la version entreprise, vous pouvez chiffrer vos partitions Linux afin de garantir un niveau de sécurité encore plus élevé. Cependant, ce n'est pas spécifique à Mongo donc nous n'en parlerons pas ici.

Nous aborderons ici la question de chiffrer la transmission de données. En effet, bien que vous ayez configuré une authentification, si vous vous connectez à votre base de données mongo depuis un client distant, tout transite en clair. En d'autres termes, il est d'une simplicité déconcertante « d'écouter » toutes les données qui transitent entre vos machines. Pour contrer cela, nous allons utiliser SSL/TLS, le même protocole de chiffrement qu'utilisent certains sites web.

Nous générons d'abord une clef autosignée pour 365 jours :

1. cd /etc/ssl/

2. openssl req -newkey rsa:4096 -new -x509 -days 365 -nodes -out mongodb-cert.crt -keyout mongodb-cert.key

4. # on crée ensuite un fichier pem en concaténant la clef et le certificat

5. cat mongodb-cert.key mongodb-cert.crt > mongodb.pem

Il faut évidemment configurer mongo pour qu'il requière des connexions chiffrées et lui indiquer l'emplacement du certificat. Comme d'habitude, on se dirige vers /etc/mongod.conf et on ajoute les infos liées au ssl dans la partie net.

1. net:

2. ssl:

3. mode: requireSSL

4. PEMKeyFile: /etc/ssl/mongodb.pem

Il est également possible d'accepter à la fois les connexions sécurisées et non sécurisées. Pour cela, il faudra renseigner le paramètre allowSSL ou preferSSL en lieu et place de requireSSL. Le premier est le plus permissif puisqu'il laisse le choix, tandis que le second peut s'avérer être un bon compromis. En effet, il laisse le choix aux clients du SSL ou non, mais il l'impose aux connexions entre serveurs.

Enfin, côté client, vous devrez spécifier qu'il faut se connecter via SSL. En outre, comme nous avons ici autosigné notre certificat et que nous ne requérons pas l'authentification via SSL, nous demanderons à Mongo d'ignorer la validation du certificat. Le SSL – qui permet à la fois de chiffrer et d'authentifier – sera ici uniquement utilisé pour le chiffrement. Pour plus d'information là-dessus, n'hésitez pas à lire la doc pour la sécurisation du serveur et du client.

mongo --ssl --sslAllowInvalidCertificates --host mongo.buzut.fr -u buzut -p azertyytreza --authenticationDatabase buzut_blog

IV-B - Sauvegarde et restauration

Enfin, une base de données doit parfois être sauvegardée et restaurée. De la même manière que mysqldump permet de faire cela chez MySQL, il est extrêmement facile de le faire avec Mongo. Dans sa forme la plus simple, il suffit d'appeler mongodump et c'est tout ! Le dump est par défaut situé dans le dossier courant. Évidemment, quelques options bien pratiques sont à connaître :

-u

1. Préciser le nom d'utilisateur.

-p

1. Préciser le mot de passe.

-d

1. Indiquer la base de données à sauvegarder. Par défaut, le dump contient toutes les bases.

-c

1. Indiquer une ou plusieurs collections à sauvegarder.

-o

1. Préciser un chemin pour l'export. - envoie les données sur la sortie standard.

Pas de sauvegarde sans restauration, nous sommes d'accord ! On utilisera pour ça mongorestore adresse_du_dump. Les options sont les mêmes que mongodump. -c permet de ne récupérer qu'une seule collection. -d indique la base de données dans laquelle importer les données. Si elle n'existe pas, elle sera créée. Si vous importez dans une base de données existante et qui n'est pas vide, mongorestore insère les nouvelles données sans modifier celles existantes. Et dans le cas où un document à importer possède le même _id qu'un document existant, ce dernier ne sera pas remplacé. Pour vider la base de données cible avant import, il existe l'option --drop.

IV-C - Gestion des index

Comme dans MySQL, les index permettent d'accélérer les requêtes en évitant au moteur de base de données de devoir scanner tous les documents de la collection afin de trouver ceux correspondant à la requête.

Voici les commandes les plus courantes :

1. # créer un index

2. db.users.createIndex({ username: 1 })

4. # créer un index et préciser que la valeur doit être unique

5. db.users.createIndex({ email: 1 }, { unique: true })

L'index ci-dessus considérera une absence de valeur comme une valeur. Il n'y a donc qu'un email qui pourra être vide, ensuite mongo considérera les autres emails sans valeur comme dupliqués et refusera de les ajouter. Pour empêcher ce comportement, on peut utiliser l'option sparse.

1. db.users.createIndex({ email: 1 }, { unique: true, sparse: true })

3. # pour rechercher en mode fulltext et non en exact match

4. # il est possible d'utiliser l'index de type text

5. db.users.createIndex({ firstname: "text" })

7. # il ne peut y avoir qu'un seul index text par collection

8. # cependant, on peut les combiner

9. db.users.createIndex({ firstname: "text", lastname: "text" })

11. # afficher les index d'une collection

12. db.collection.getIndexes()

14. # supprimer un index (le nom de l'index est fourni par getIndexes)

15. db.collection.dropIndex("indexName")

17. # supprimer tous les index d'une collection

18. db.collection.dropIndexes()

20. # supprimer tous les index de la base

21. db.getCollectionNames().forEach(function(collection) {

22. db[collection].dropIndexes();

23. });

IV-D - Données et statistiques

Vous n'êtes pas sans savoir que, comme dans le monde SQL, il existe chez Mongo plusieurs moteurs de stockage.

Il est intéressant de savoir ce qui se passe dans sa base. Quel moteur de stockage est utilisé, quels sont les index, combien il y en a, quelle taille font les collections… Voici quelques commandes bien pratiques :

1. # pour avoir toute sorte d'infos sur le serveur (paramètres de config etc)

2. db.serverStatus()

4. # connaître en un clin d'œil le moteur de stockage

5. db.serverStatus().storageEngine

7. # si votre moteur est WiredTigger, pour en afficher la configuration

8. db.serverStatus().wiredTiger

10. # lister les index dans une collection

11. db.users.getIndexes()

13. # lister les index dans toute la base

14. db.getCollectionNames().forEach(function(collection) {

15. indexes = db[collection].getIndexes();

16. print("Indexes for " + collection + ":");

17. printjson(indexes);

18. });

20. # connaître la taille des index en mémoire (mieux vaut que ça rentre dans la RAM)

21. db.collection.totalIndexSize()

Pour la route, la doc de PHP propose une table des correspondances entre les commandes SQL et Mongo.

Et vous, comment s'est passée la transition du SQL à Mongo ? Lequel préférez-vous ?

V - Note de la rédaction de Developpez.com

Nous tenons à remercier Quentin Busuttil qui nous a aimablement autorisés à publier son tutoriel : Commandes essentielles de MongoDB. Nous remercions également Winjerome pour la mise au gabarit et f-leb pour la relecture orthographique.