MongoDB: la vida cambia, tus datos también. Operaciones de actualización simples

MongoDB: la vida cambia, tus datos también. Operaciones de actualización simples
Sin comentarios Facebook Twitter Flipboard E-mail

En el anterior artículo sobre MongoDB estuvimos hablando sobre la inserción de datos. Es sin duda la operación más sencilla que se puede realizar ya que solo tenemos que tener en cuenta algunos detalles. Creamos nuestro documento JSON, y lo almacenamos en MongoDB. Es muy fácil.

Actualizar datos es algo más complejo. Primero porque hay que usar una consulta para filtrar los documentos y otra sentencia para actualizar los campos. Y segundo porque las actualizaciones de datos, pueden implicar que MongoDB tenga que mover documentos. Y eso afecta al rendimiento. Además la consultas pueden ser complicadas si tenemos que actualizar arrays o subdocumentos.

En este artículo vamos a tratar la modificación de datos sencillos. Para que el artículo no quede demasiado denso, la actualización de arrays y subdocumentos la trataremos en el siguiente artículo.

Manos a la obra.

La sentencia update

Para actualizar datos, podemos utilizar la sentencia update. Esta sentencia funciona de manera similar a como funciona en una base de datos relacional. Veamos un ejemplo. Supongamos que tenemos el siguiente documento:

{
  "_id" : ObjectId("5305eae128222ca13a01b039"),
  "name" : "Walter White",
  "profession" : "Teacher",
  "hobbies" : [ 
      "make drugs", 
      "killing flies"
  ],
  "killed" : 0
}

Para actualizar ese documento con una sentencia update, la consulta sería similar a esta:

db.genbeta.update(
  {"_id" : ObjectId("5305eae128222ca13a01b039")},
  {"name" : "Heinsenberg"}
)

La primera parte de la sentencia, la que contiene el campo _id, es la que utilizamos para filtrar. Es como si en SQL hicieramos un "Where _id = '5305eae128222ca13a01b039'".

La segunda parte de la sentencia, expresa el valor que tendrá el documento tras la actualización. Y esto es importante, no le estamos diciendo a MongoDB que modifique solo el campo name. Estamos diciendo que modifique el documento entero. Si después de actualizar, hacemos una búsqueda con un db.genbeta.find() el documento tendrá este aspecto:

{
  "_id" : ObjectId("5305eae128222ca13a01b039"),
  "name" : "Heinsenberg"
}   

Se puede ver, que MongoDB ha modificado el documento por completo.

Utilizando el modificador $set

Volviendo al documento original, vamos a hacer la misma operación que antes, pero en esta ocasión modificando solo el campo name. Para ello utilizamos el modificador $set.

db.genbeta.update(
  {"_id" : ObjectId("5305eae128222ca13a01b039")},
  {"$set":{"name":"Heinsenberg"}}
)

En este caso añadimos el operador $set al comando. El campo afectado se actualiza con el nuevo valor. El resto de campos no se modifican. El documento quedaría así.

{
  "_id" : ObjectId("5305eae128222ca13a01b039"),
  "name" : "Heinsenberg",
  "profession" : "Teacher",
  "hobbies" : [ 
      "make drugs", 
      "killing flies"
  ],
  "killed" : 0
}

Para realizar la operación inversa, es decir, eliminar un campo del documento, utilizaríamos el comando $unset. Por ejemplo para eliminar el campo killed utilizaríamos {"$unset":{"killed":""}}.

Otros modificadores útiles

Además de $set y $unset, tenemos otros modificadores que nos pueden ayudar mucho a la hora de actualizar datos. Por ejemplo tenemos $inc que suma o resta un valor a un campo numérico. Por ejemplo para incrementar en 5 el campo killed usaríamos el siguiente comando:

db.genbeta.update(
  {"_id" : ObjectId("5305eae128222ca13a01b039")},
  {"$inc":{"killed":5}}
)

Eso sumaría 5 al valor existente en el campo. Si el campo no existe, lo crea y le añade 5.

Si lo que queremos es restar una determinada cantidad, hay que pasar un valor negativo.

db.genbeta.update(
  {"_id" : ObjectId("5305eae128222ca13a01b039")},
  {"$inc":{"killed":-3}}
)

Otro operador que podemos utilizar es $rename. Sirve para cambiar el nombre de un campo existente. Por ejemplo, lo podemos utilizar sobre nuestro documento de ejemplo de la siguiente manera:

db.genbeta.update(
  {"_id" : ObjectId("5305eae128222ca13a01b039")},
  {"$rename":{"killed":"peopleKilled"}}
)

Tras ejecutar este comando nuestro documento quedaría así:

{
  "_id" : ObjectId("5305eae128222ca13a01b039"),
  "name" : "Heinsenberg",
  "profession" : "Teacher",
  "hobbies" : [ 
      "make drugs", 
      "killing flies"
  ],
  "peopleKilled" : 2
}

Parámetros especiales de update

Los ejemplos anteriores utilizan el _id del documento para filtrar los documentos que hay que actualizar. Como consecuencia solo se actualiza un documento, ya que este campo siempre es único.

Pero aunque nuestro filtro devolviese más de un documento, MongoDB solo actualizaría uno de ellos. Para que MongoDB actualice más de un documento con una sentencia update, se lo tenemos que decir de forma expresa con el parámetro multi.

Si queremos insertar los documentos cuando no existan, podemos utilizar el parámetro upsert. En ese caso el documento se insertará en el caso de que no exista.

Ligado al parámetro upsert tenemos el modificador $setOnInsert. Este modificador sirve para realizar operaciones distintas, dependiendo de si MongoDB tiene que realizar un update o un insert. Veamos un ejemplo que incluye todas estas opciones.

db.genbeta.update(
{"peopleKilled" : 2},
{
    "$setOnInsert" : {"name" : "Jesse Pinkman"},
    "$inc" : {"peopleKilled" : 4}
},
{"multi" : true,
"upsert" : true}  
)

Si hacemos un db.genbeta.find(), tendremos como resultado los siguientes documentos.

{
    "_id" : ObjectId("5305eae128222ca13a01b039"),       
    "name" : "Heinsenberg",     
    "profession" : "Teacher",
    "peopleKilled" : 6,
    "hobbies" : [ 
        "make drugs", 
        "killing flies"
    ]
},
{
    "_id" : ObjectId("5306327d2d9d2522fde51db7"),
    "name" : "Jesse Pinkman",
    "peopleKilled" : 4
}

Ahora tenemos dos documentos. El primero es el que ya teníamos en nuestra colección. En este caso se ha llevado a cabo una actualización, por lo que se ignora el modificador $setOnInsert.

Con el segundo documento, el caso es el contrario. Al ser una inserción, y añadir un nuevo documento, se incluye además de peopleKilled el campo name.

También vemos que en esta ocasión hemos cambiado el filtro de la consulta. Ahora estamos buscando todos los documentos que tengan un campo peopleKilled igual a 2. Si tuviéramos más documentos en la colección, la sentencia podría actualizar todos los que cumplen ese criterio, ya que hemos incluido el parámetro multi.

Consideraciones de rendimiento

Para entender bien los problemas de rendimiento que puede acarrear una sentencia de actualización, hay que entender como son almacenados los datos.

Cuando una colección está vacía y los documentos comienzan a insertarse, MongoDB los guarda de forma contigua en el disco. Sin ningún espacio entre ellos.

En algún momento se puede dar el caso de que uno de esos documentos necesite ser actualizado para añadir más datos. Por ejemplo nuevos valores en algún campo array. El documento, por tanto, necesita más espacio. Como los documentos se han insertado sin espacio entre ellos, MongoDB tiene que mover el documento a una zona con espacio libre. Esta operación es bastante costosa y penaliza mucho el rendimiento de las actualizaciones.

Una vez se ha movido el documento, MongoDB aumenta el factor de separación. Con este factor de separación, MongoDB se adapta a las actualizaciones de datos de forma dinámica, para intentar minimizar el movimiento de documentos. Al aumentar el valor del factor de separación, los nuevos documentos se añadirán con espacio libre entre ellos. Si hay nuevas actualizaciones que impliquen movimiento de documentos, MongoDB continuará aumentando el factor de separación, por lo que los nuevos documentos insertados se almacenarán con aun más espacio libre entre ellos. El factor de separación se irá reduciendo a medida que las actualizaciones de datos, dejen de necesitar mover documentos.

Como vemos, las actualizaciones de datos, pueden afectar gravemente al rendimiento. Si MongoDB tiene que mover los documentos en cada actualización, la tasa de actualizaciones por segundo caerá de forma drástica. Tenemos que intentar que nuestro factor de separación este siempre cercano a 1. Si no existe separación entre documentos, tendremos una indicación de que nuestros documentos no están creciendo sin control. Por eso es importante que el esquema de nuestra aplicación esté bien diseñado.

Y de momento lo dejamos aquí. En el próximo artículo veremos como actualizar arrays, subdocumentos o como actualizar a través de la función save.

En GenbetaDev | Bases de datos NoSQL. Elige la opción que mejor se adapte a tus necesidades, MongoDB: empezando por el principio. Insertando datos Imagen | thebarrowboy

Comentarios cerrados
Inicio