Статьи

Двигаясь вместе с PyMongo

В предыдущем посте я представил MongoDB и его драйвер Python pymongo . Здесь я продолжаю в том же духе, более подробно описывая, как вы можете стать продуктивным, используя pymongo.

Подключение к базе данных

В последнем посте я дал обзор кратко о том , как подключиться к базе данных , размещенного на MongoLab службы с использованием PyMongo, поэтому я не буду вдаваться в подробности о том , что здесь. Вместо этого я упомяну несколько вариантов, о которых вы хотели бы знать.

Некоторые параметры подключения устанавливают политику по умолчанию для безопасности ваших данных:

  • Безопасный MongoDB по умолчанию работает в режиме «запусти и забудь», где все операции по изменению данных оптимистично предполагаются успешными. Включение безопасного режима меняет это, ожидая ответа от сервера базы данных, указывающего, что операция прошла успешно или не удалась, прежде чем вернуться из операции изменения данных.
  • Журнал В версии 1.8 MongoDB ввел журналирование для обеспечения долговечности одного сервера. Однако гибкий подход MongoDB к обеспечению баланса между безопасностью и производительностью означает, что если ваше приложение хочет убедиться, что его данные действительно были сохранены, вам нужно дождаться записи журнала.
  • fsync Это действительно очень безопасный вариант. С журналом или без него будет ждать, пока ваши данные не окажутся на физическом диске, прежде чем они вернутся из операций обновления.
  • w До ведения журнала MongoDB использовала (и до сих пор использует) репликацию для обеспечения надежности ваших данных. Параметр w позволяет вам контролировать количество серверов (или набор серверов), на которые было реплицировано ваше обновление, прежде чем вернуться из операции модификации данных. Имейте в виду, что этот параметр может значительно замедлить ваши обновления, особенно если вы требуете их репликации в разные центры обработки данных.
  • read_preference Этот параметр позволяет вам указать, как вы хотите обрабатывать запросы. По умолчанию даже в конфигурации набора реплик все ваши запросы будут перенаправляться на основной сервер в наборе реплик, чтобы обеспечить надежную согласованность. Используя read_preference, вы можете изменить эту политику, позволяя вашим запросам распределяться по вторичным узлам набора реплик для повышения производительности за счет перехода к «конечной согласованности».

Что хорошо в соединении pymongo, так это то, что оно автоматически объединяется. Это означает, что pymongo поддерживает пул соединений с сервером mongodb, который он повторно использует в течение всего времени жизни вашего приложения. Это хорошо для производительности, так как это означает, что pymongo не нужно проходить через накладные расходы при установлении соединения каждый раз, когда он выполняет операцию. В основном это происходит автоматически. тем не менее, вам необходимо знать о пуле соединений, поскольку вам нужно вручную уведомить pymongo о том, что вы «сделали» соединение в пуле, чтобы его можно было использовать повторно.

Теперь, когда все это не так, самый простой способ подключения к базе данных MongoDB из python приведен ниже (при условии, что вы используете сервер MongoDB локально и что вы установили ipython и pymongo):

In [1]: import pymongo

In [2]: conn = pymongo.Connection()

 

Вставка документов

Вставка документов начинается с выбора базы данных. Чтобы создать базу данных, вы делаете … ну, вообще ничего. При первом обращении к базе данных сервер MongoDB автоматически создает ее для вас. Поэтому, когда у вас есть база данных, вам нужно решить, в какой «коллекции» хранить ваши документы. Чтобы создать коллекцию, вы делаете … правильно — ничего. Итак, поехали:

In [3]: db = conn.tutorial

In [4]: db.test
Out[4]: Collection(Database(Connection('localhost', 27017), u'tutorial'), u'test')

In [5]: db.test.insert({'name': 'My Document', 'ids': [1,2,3], 'subdocument': {'a':2}})
Out[5]: ObjectId('4f25bcffeb033049af000000')

Здесь следует отметить, что команда вставки вернула нам значение ObjectId. Это значение, сгенерированное pymongo для свойства _id, «первичного ключа» документа MongoDB. Мы также можем указать _id вручную, если захотим (и нам не нужно использовать ObjectIds):

In [6]: db.test.insert({'_id': 42, 'name': 'My Document', 'ids': [1,2,3], 'subdocument': {'a':2}})
Out[6]: 42

 

Примечание о структуре документа

Примечание здесь — это заказ о том, какие документы вы можете вставить в коллекцию. Технически тип документа описывается спецификацией BSON , но практически вы можете думать о нем как о JSON плюс несколько дополнительных типов. Некоторые из типов, которые вы должны знать:

  • типы примитивов Python int, строки и числа с плавающей точкой автоматически обрабатываются Pymongo соответствующим образом.
  • Объект Объекты представлены pymongo как обычные символы Python. Технически, в BSON объекты являются упорядоченными словарями, поэтому, если вам нужно или вы хотите положиться на упорядочение, вы можете использовать модуль bson, входящий в состав pymongo, для создания таких объектов. Объекты имеют строки в качестве ключей и могут иметь любой допустимый тип BSON в качестве своих значений.
  • массив Массивы представлены pymongo в виде списков Python. Опять же, любой допустимый тип BSON может использоваться в качестве значений, и значения в массиве не обязательно должны быть одного типа.
  • ObjectId ObjectId можно рассматривать как глобально уникальные идентификаторы. Они часто используются для генерации «безопасных» первичных ключей для коллекций без дополнительных затрат на использование генератора последовательностей, как это часто делается в реляционных базах данных.
  • Двоичные строки в BSON хранятся в кодировке UTF-8 в кодировке Unicode. Для хранения данных, не относящихся к Юникоду, вы должны использовать оболочку bson.Binary вокруг строки Python.

Документы в MongoDB также имеют ограничение по размеру (16 МБ в последней версии), которого достаточно для многих сценариев использования, но об этом вы должны знать при разработке своих схем.

Пакетные вставки

MongoDB и pymongo также позволяют вставлять несколько документов с помощью одного вызова API (и одной поездки на сервер). Это может значительно ускорить вставку и полезно для таких вещей, как пакетная загрузка. Чтобы выполнить мульти-вставку, вы просто передаете список документов методу insert (), а не одному документу:

In [7]: db.test.insert([{'a':1}, {'a':2}])
Out[7]: [ObjectId('4f25c0aceb033049af000001'), ObjectId('4f25c0aceb03

Возможно, вы заметили, что структура документов, вставленных в этот фрагмент, (значительно!) Отличается от документов, вставленных ранее. MongoDB не предъявляет никаких требований к тому, чтобы документы разделяли структуру друг с другом. Аналогично динамической типизации Python, MongoDB — это динамически типизированная база данных, в которой структура документа хранится вместе с самим документом. Практически, я нашел полезным сгруппировать документы с аналогичной структурой в коллекции, но это определенно не жесткое правило.

Запросы

Хорошо, теперь, когда вы поместили свои данные в базу данных, как вы можете вернуть их обратно? Это функция метода find () для объектов коллекции. Без параметров он вернет все документы в коллекции как итератор Python:

In [8]: db.test.find()
Out[8]: <pymongo.cursor.Cursor at 0x7f2910068b90>

In [9]: list(db.test.find())
Out[9]: 
[{u'_id': ObjectId('4f25bce9eb033049a0000000'),
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}},
 {u'_id': ObjectId('4f25bcffeb033049af000000'),
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}},
 {u'_id': 42,
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}},
 {u'_id': ObjectId('4f25c0aceb033049af000001'), u'a': 1},
 {u'_id': ObjectId('4f25c0aceb033049af000002'), u'a': 2}]

Однако в большинстве случаев вам нужно выбрать конкретные документы для возврата. Вы делаете это, предоставляя запрос в качестве первого параметра для поиска. Запросы также представлены в виде объектов BSON и аналогичны запросам на примере, которые вы, возможно, видели в других технологиях баз данных. Например, чтобы получить все документы с именем «Мой документ», вы должны использовать следующий запрос:

In [13]: list(db.test.find({'name':'My Document'}))
Out[13]: 
[{u'_id': ObjectId('4f25bce9eb033049a0000000'),
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}},
 {u'_id': ObjectId('4f25bcffeb033049af000000'),
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}},
 {u'_id': 42,
  u'ids': [1, 2, 3],
  u'name': u'My Document',
  u'subdocument': {u'a': 2}}]

MongoDB также позволяет вам «проникать внутрь» встроенных документов, используя точечную запись. Вот несколько примеров того, как вы можете это использовать:

In [22]: db.testq.insert([
   ....:         { 'a': { 'b': 1 }, 'c': [{'d': 1}, {'d':2}, {'d':3}]},
   ....:         { 'a': { 'b': 2 }, 'c': [{'d': 2}, {'d':3}, {'d':4}]},
   ....:         { 'a': { 'b': 3 }, 'c': [{'d': 3}, {'d':4}, {'d':5}]}
   ....:         ])
Out[22]: 
[ObjectId('4f25c89beb033049af000009'),
 ObjectId('4f25c89beb033049af00000a'),
 ObjectId('4f25c89beb033049af00000b')]

In [23]: # reach inside objects

In [24]: list(db.testq.find({'a.b': 1}))
Out[24]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

In [25]: list(db.testq.find({'a.b': 2}))
Out[25]: 
[{u'_id': ObjectId('4f25c89beb033049af00000a'),
  u'a': {u'b': 2},
  u'c': [{u'd': 2}, {u'd': 3}, {u'd': 4}]}]

In [26]: # find objects where *any* value in an array matches 

In [27]: list(db.testq.find({'c': {'d':1}}))
Out[27]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

In [28]: # reach into an array

In [29]: list(db.testq.find({'c.d': 2}))
Out[29]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]},
 {u'_id': ObjectId('4f25c89beb033049af00000a'),
  u'a': {u'b': 2},
  u'c': [{u'd': 2}, {u'd': 3}, {u'd': 4}]}]

In [30]: list(db.testq.find({'c.1.d': 2}))
Out[30]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

Еще одна важная вещь, о которой следует знать, это то, что вы можете вернуть только подмножество полей в запросе. (По умолчанию _id всегда возвращается, если вы явно не подавляете его.) Вот пример:

In [31]: list(db.testq.find({'c.1.d':2}, {'c':1}))
Out[31]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

In [32]: # we can also reach inside when specifying which fields to return

In [33]: list(db.testq.find({'c.1.d':2}, {'a.b':1}))
Out[33]: [{u'_id': ObjectId('4f25c89beb033049af000009'), u'a': {u'b

Мы также можем ограничить количество результатов, возвращаемых запросом, пропустив некоторые документы и ограничив общее число возвращаемых:

In [66]: list(db.testq.find())
Out[66]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]},
 {u'_id': ObjectId('4f25c89beb033049af00000a'),
  u'a': {u'b': 2},
  u'c': [{u'd': 2}, {u'd': 3}, {u'd': 4}]},
 {u'_id': ObjectId('4f25c89beb033049af00000b'),
  u'a': {u'b': 3},
  u'c': [{u'd': 3}, {u'd': 4}, {u'd': 5}]}]

In [67]: list(db.testq.find().skip(1).limit(1))
Out[67]: 
[{u'_id': ObjectId('4f25c89beb033049af00000a'),
  u'a': {u'b': 2},
  u'c': [{u'd': 2}, {u'd': 3}, {u'd': 4}]}]

В запросах и расширенный запрос страница в документации MongoDB предоставляет полный синтаксис запроса, который включает в себя неравенство, размер и типа операция, и многое другое.

индексирование

Как и любая база данных, MongoDB может работать так хорошо, только сканируя коллекции на совпадения. Чтобы обеспечить лучшую производительность, MongoDB может использовать индексы для своих коллекций. Обычный метод указания индекса в MongoDB заключается в том, чтобы «обеспечить» его существование в соответствии с тем, как вещи возникают, когда они необходимы. Например, чтобы создать индекс для нашей «тестовой» коллекции, мы должны сделать следующее:

In [13]: db.test.drop()

In [14]: db.test.ensure_index('a')
Out[14]: u'a_1'

In [15]: db.test.insert([
   ....: {'a': 1, 'b':2}, {'a':2, 'b':3}, {'a':3, 'b':4}])
Out[15]: 
[ObjectId('4f28261deb033053bc000000'),
 ObjectId('4f28261deb033053bc000001'),
 ObjectId('4f28261deb033053bc000002')]

In [16]: db.test.find({'a':2})
Out[16]: <pymongo.cursor.Cursor at 0x24f7b90>

In [17]: db.test.find_one({'a':2})
Out[17]: {u'_id': ObjectId('4f28261deb033053bc000001'), u'a': 2, u'

Хорошо, ну, на самом деле это не так уж интересно. Однако MongoDB предоставляет метод объяснения (), который позволяет нам увидеть, используется ли наш индекс:

In [18]: db.test.find({'a':2}).explain()
Out[18]: 
{u'allPlans': [{u'cursor': u'BtreeCursor a_1',
   u'indexBounds': {u'a': [[2, 2]]}}],
 u'cursor': u'BtreeCursor a_1',
 u'indexBounds': {u'a': [[2, 2]]},
 u'indexOnly': False,
 u'isMultiKey': False,
 u'millis': 0,
 u'n': 1,
 u'nChunkSkips': 0,
 u'nYields': 0,
 u'nscanned': 1,
 u'nscannedObjects': 1}

Здесь есть несколько важных моментов, которые стоит отметить. Наиболее важным является тип курсора, BtreeCursor a_1. Это означает, что он использует индекс и, в частности, индекс с именем a_1, который мы создали выше. Если поле не проиндексировано, мы получаем другой план запроса от MongoDB:

In [19]: db.test.find({'b':2}).explain()
Out[19]: 
{u'allPlans': [{u'cursor': u'BasicCursor', u'indexBounds': {}}],
 u'cursor': u'BasicCursor',
 u'indexBounds': {},
 u'indexOnly': False,
 u'isMultiKey': False,
 u'millis': 0,
 u'n': 1,
 u'nChunkSkips': 0,
 u'nYields': 0,
 u'nscanned': 3,
 u'nscannedObjects': 3}

Здесь MongoDB использует BasicCursor. Для вас, экспертов по SQL, это эквивалентно полному сканированию таблиц и очень неэффективно. Также обратите внимание, что когда мы запрашивали индексированное поле, nscanned и nscannedObjects были обоими, это означало, что он должен был проверить только один объект, чтобы удовлетворить запрос, тогда как в случае нашего неиндексированного поля мы должны были проверить каждый документ в коллекции.

MongoDB имеет чрезвычайно быстрый запрос, который он может использовать в некоторых случаях, когда ему не нужно сканировать какие-либо объекты, только записи индекса. Это происходит, когда единственные данные, которые вы возвращаете из запроса, являются частью индекса:

In [36]: db.test.find({'a':2}, {'a':1, '_id':0}).explain()
Out[36]: 
...
 u'indexBounds': {u'a': [[2, 2]]},
 u'indexOnly': True,
 u'isMultiKey': False,
...

Обратите внимание, что поле indexOnly имеет значение true, указывая, что MongoDB должен был только проверять индекс (а не фактические данные коллекции), чтобы удовлетворить запрос.

Еще одна приятная вещь в системе индексов MongoDB — это то, что она может использовать составные индексы (индексы, которые включают более одного поля) для удовлетворения некоторых запросов. В этом случае вы должны указать направление каждого поля, так как MongoDB хранит индексы как B-деревья. Иллюстрация, вероятно, лучше всего. Во-первых, мы отбросим наш индекс a_1 и обеспечим новый индекс для a и b по возрастанию:

In [44]: db.test.drop_index('a_1')

In [45]: db.test.ensure_index([('a', 1), ('b', 1)])
Out[45]: u'a_1_b_1'

Теперь давайте посмотрим, что происходит, когда мы запрашиваем только:

In [55]: db.test.find({'a': 2}).explain()
Out[55]: 
...
 u'cursor': u'BtreeCursor a_1_b_1',
 u'indexBounds': {u'a': [[2, 2]],
  u'b': [[{u'$minElement': 1}, {u'$maxElement': 1}]]},
...

Здесь оптимизатор MongoDB достаточно «умен», чтобы использовать составной индекс (a, b) для запроса только значения a. Что если мы запросим только значение b?

In [56]: db.test.find({'b': 2}).explain()
Out[56]: 
{u'allPlans': [{u'cursor': u'BasicCursor', u'indexBounds': {}}],
 u'cursor': u'BasicCursor',
...

К сожалению! Это не работает, потому что индекс отсортирован по ключам (a, b). Порядок ключей также становится важным, когда мы хотим отсортировать наши результаты:

In [64]: db.test.find().sort([ ('a', 1), ('b', 1)]).explain()
Out[64]: 
...
 u'cursor': u'BtreeCursor a_1_b_1',
 u'indexBounds': {u'a': [[{u'$minElement': 1}, {u'$maxElement': 1}]],
  u'b': [[{u'$minElement': 1}, {u'$maxElement': 1}]]},
...

In [65]: db.test.find().sort([ ('a', 1), ('b', -1)]).explain()
Out[65]: 
{u'allPlans': [{u'cursor': u'BasicCursor', u'indexBounds': {}}],
 u'cursor': u'BasicCursor',
...

Поэтому, если мы сортируем в том же порядке, что и наш индекс, мы можем использовать индекс B-дерева для сортировки результатов. Если мы сортируем в другом порядке, мы не можем, поэтому MongoDB должен фактически загрузить весь набор результатов в ОЗУ и отсортировать его там. (MongoDB может фактически использовать индекс для точного обратного порядка сортировки, поэтому [(a, -1), (b, -1)] работали бы очень хорошо.) В коллекции из 3 документов это не Это не важно, но по мере роста ваших данных это может стать довольно медленным, в некоторых случаях фактически возвращая ошибку, потому что результирующий набор слишком велик для сортировки в ОЗУ.

Удаление данных

Удаление данных в MongoDB довольно просто. Все, что вам нужно сделать, это передать запрос методу remove () в коллекции, и MongoDB удалит все документы из коллекции, которые соответствуют запросу. (Обратите внимание, что удаление может все еще быть медленным, если вы укажете запрос неэффективно, так как они используют индексы, как и запросы).

In [72]: list(db.testq.find())
Out[72]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]},
 {u'_id': ObjectId('4f25c89beb033049af00000a'),
  u'a': {u'b': 2},
  u'c': [{u'd': 2}, {u'd': 3}, {u'd': 4}]},
 {u'_id': ObjectId('4f25c89beb033049af00000b'),
  u'a': {u'b': 3},
  u'c': [{u'd': 3}, {u'd': 4}, {u'd': 5}]}]

In [73]: db.testq.remove({'a.b': {'$gt': 1}})

In [74]: list(db.testq.find())
Out[74]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 1},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

 

Обновление данных

Во многих случаях обновление в MongoDB так же просто, как и вызов save () для команды python:

In [76]: doc = db.testq.find_one({'a.b': 1})

In [77]: doc['a']['b'] = 4

In [78]: db.testq.save(doc)
Out[78]: ObjectId('4f25c89beb033049af000009')

In [79]: list(db.testq.find())
Out[79]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 4},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

Кроме того, save () можно использовать для вставки документов, если они еще не существуют (эта проверка выполняется путем проверки необходимости сохранения для ключа _id).

В отличие от некоторых других решений NoSQL , MongoDB позволяет вам выполнять быстрое обновление документов на месте, используя специальные [операции модификатора] [модификатор mongodb]. Например, чтобы установить поле для определенного значения, вы можете сделать следующее:

In [81]: db.testq.update({'a.b': 4}, {'$set': {'a.b': 7}})

In [82]: list(db.testq.find())
Out[82]: 
[{u'_id': ObjectId('4f25c89beb033049af000009'),
  u'a': {u'b': 7},
  u'c': [{u'd': 1}, {u'd': 2}, {u'd': 3}]}]

MongoDB предоставляет несколько различных модификаторов, которые можно использовать для обновления документов на месте, включая следующие (более подробную информацию см. В обновлениях ):

  • $ inc Увеличение числового поля (обобщенно; может увеличиваться на любое число)
  • $ set Установить определенные поля для новых значений
  • $ unset Удалить поле из документа
  • $ push Добавить значение в массив в документе
  • $ pushAll Добавить несколько значений в массив
  • $ addToSet Добавить значение в массив, если и только если оно еще не существует
  • $ pop Удалить последнее (или первое) значение массива
  • $ pull Удалить все вхождения значения из массива
  • $ pullAll Удалить все вхождения любого из набора значений из массива
  • $ rename Переименовать поле
  • Побитовые обновления $

Я могу рассказать гораздо больше, но надеюсь, что это поднимет вам аппетит, чтобы больше узнать о MongoDB. В следующих статьях я расскажу, как использовать GridFS («файловая система» поверх MongoDB) и различные параметры агрегирования MongoDB, а также как вы можете использовать Ming для упрощения определенных операций.

Источник: http://blog.pythonisito.com/2012/01/moving-along-with-pymongo.html