PyMongo 常見問題

這是一篇翻譯文章,原連接在這裏。翻譯可能不許確,歡迎指出文章中存在的問題。html

PyMongo是線程安全的嗎

PyMongo是線程安全的,而且爲多線程應用提供了內置的鏈接池python

PyMongo是進程安全的嗎

PyMongo不是進程安全的,若是你在fork()中使用MongoClient實例,必須當心。具體來講,MongoClient實例不能從父進程複製到子進程,父進程和每一個子進程必須建立屬於本身的MongoClient實例。因爲自己的不兼容性,在子進程中使用從父進程複製的MonogoClient實例頗有可能發生死鎖。PyMongo會在有可能引發死鎖的狀況下發出警告。git

MongoClient產生多個線程來運行後臺任務,如監視鏈接服務器。這些線程共享受Lock實例(進程不安全)保護的狀態,因此,MongoClient受到與其餘使用鎖(互斥)的多線程程序同樣的限制,其中一個限制是在使用fork()後鎖失效。在fork過程當中,全部鎖都會被複制到子進程中,而且與父進程保持相同的狀態:若是父進程中是鎖定的,子進程複製的鎖也是鎖定的。由fork()建立的子進程只有一個線程,因此在這個子線程中任何從父進程中任何子線程中取出的鎖都不會被釋放,當這個子線程嘗試獲取其中一個鎖時,會發生死鎖。github

有關在fork()使用多線程上下文中的Python鎖的問題,請參閱bugs.python.org/issue6721web

鏈接池在PyMongo中是如何工做的

每一個MongoClient實例在每一個MongoDb服務器都有一個內置的鏈接池,這些鏈接池會當即打開socket,用來支持多線程應用所需的併發操做MongoDB數量。這些socket沒有線程相關性。mongodb

每一個鏈接池的大小被限制在maxPoolSize,默認值爲100.若是存在maxPoolSize個到服務器的鏈接而且這些鏈接所有在使用中,那麼到該服務器的下一個請求會一直等待,直到其中一個鏈接可用。shell

客戶端實例在MongoDB集羣中的每一個服務器上額外打開一個socket來監視服務器的狀態。數據庫

例如:一個鏈接到3個節點主從複製服務器的客戶端將打開3個監視socket。它還能夠根據須要打開更多個socket(最多maxPoolSize)來支持每一個服務器上多線程應用的併發操做。在maxPoolSize爲100的狀況下,若是應用只使用主鏈接,則只有主鏈接池的鏈接數增長(最多103)。若是應用使用ReadPreference查詢輔助數據庫,則它的鏈接池的鏈接數也會增長,總鏈接數能夠達到303.django

能夠經過使用minPoolSize(默認0)參數來設置每一個服務器的最小併發鏈接數。鏈接池將初始化minPoolZise個socket。若是因爲網絡緣由致使socket關閉,致使socket的鏈接數(使用中和空閒中)降低到最小值如下,則會打開新的socket,直到socket的數量達到最小值。json

可使用maxIdleTime參數來設置一個鏈接在鏈接池中保持空閒的最大毫秒數,以後它將被刪除或者替換,默認值爲None(沒有限制)。

MongoClient的默認配置適用於大多數應用:

client = MongoClient(host, port)
複製代碼

爲每一個進程建立一次客戶端,並將其重用於全部操做。爲每一個請求建立一個新的客戶端是一個常見的錯誤,由於這樣很是低效。

要在一個進程中支持極高數量的併發MongoDB操做,需增長maxPoolSize:

client = MongoClient(host, port, maxPoolSize=200)
複製代碼

或者使其沒有限制:

client = MongoClient(host, port, maxPoolSize=None)
複製代碼

默認狀況下,容許任意數量的線程等待socket可用,而且能夠等待任意長的時間。能夠設置waitQueueMultiple參數來限制等待線程的數量。例如:限制等待數量不大於500:

client = MongoClient(host, port, maxPoolSize=50, waitQueueMultiple=10)
複製代碼

當已經由500個線程正在等待socket時,第501個須要socket的線程將拋出ExceededMaxWaiters。使用waitQueueMultiple能夠如今加載峯值期間應用中排隊的數量,可是會引發額外的異常。

一旦鏈接池達到最大值,另外的線程能夠無限等待socket可用,除非你設置了waitQueueTimeoutMS:

client = MongoClient(host, port, waitQueueTimeoutMS=100)
複製代碼

在這個例子中,一個線程若是等待socket的時間超過100ms,它將拋出ConnectionFailure錯誤。waitQueueTimeoutMS適用於在加載峯值期間限制操做的持續時間比完成每一個操做更重要的情景。

當任何線程調用close()時, 全部閒置的socket都會被關閉,全部正在使用的socket將在它返回鏈接池時被關閉。

PyMongo支持Python3 嗎?

PyMongo支持CPython3.4+和PyPy3。詳情請參閱Python3 FAQ

PyMongo是否支持Gevent,asyncio,Tornado或Twisted等異步框架?

PyMongo徹底支持Gevent

要將MongoDB與asyncioTornado一塊兒使用,請參閱Motor項目。

對於Twisted,請參閱TxMongo

爲何PyMongo將一個_id字段添加到我全部的文檔中?

當使用insert_one(),insert_many()或者bulk_write()向MongoDB中插入一個文檔時,若是文檔沒有_id字段,PyMongo將自動加上_id字段,其值爲ObjectId的一個實例。例如:

>>> my_doc = {'x': 1}
>>> collection.insert_one(my_doc)
<pymongo.results.InsertOneResult object at 0x7f3fc25bd640>
>>> my_doc
{'x': 1, '_id': ObjectId('560db337fba522189f171720')}
複製代碼

當調用insert_many()向單個文檔插入一個引用列表時,常常會引發BulkWriteError錯誤。這是幾個Python習慣引發的:

>>> doc = {}
>>> collection.insert_many(doc for _ in range(10))
Traceback (most recent call last):
...
pymongo.errors.BulkWriteError: batch op errors occurred
>>> doc
{'_id': ObjectId('560f171cfba52279f0b0da0c')}

>>> docs = [{}]
>>> collection.insert_many(docs * 10)
Traceback (most recent call last):
...
pymongo.errors.BulkWriteError: batch op errors occurred
>>> docs
[{'_id': ObjectId('560f1933fba52279f0b0da0e')}]
複製代碼

PyMongo以這種方式添加_id字段有如下幾個緣由:

  • 全部的MongoDB文檔都必須由一個_id字段。
  • 若是PyMongo插入補個不帶有_id字段的文檔,MongoDB會本身添加,而且不會返回_id字段給PyMongo。
  • 在添加_id字段以前複製要插入的文檔對於大多數高寫入的應用而言代價是極其昂貴的。

若是你不但願PyMongo向文檔中添加_id字段,則只能插入已有_id字段的文檔。

副本中的鍵順序-爲何查詢在shell是有序的,在PyMongo中無序?

BSON文檔中的鍵值對能夠是任何順序(除了_id始終是第一個)。在讀寫數據是,mongo shell按鍵保持順序。下面的例子中請注意在插入是'b'在'a'前面,查詢時也同樣:

> // mongo shell.
> db.collection.insert( { "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } } )
WriteResult({ "nInserted" : 1 })
> db.collection.find()
{ "_id" : 1, "subdocument" : { "b" : 1, "a" : 1 } }
複製代碼

PyMongo在默認狀況下將BSON文檔表示爲Python字典,而且沒有字典中鍵的順序。也就是說,聲明Python字典時,'a'在前面或者'b'在前面是同樣的。

>>> print({'a': 1.0, 'b': 1.0})
{'a': 1.0, 'b': 1.0}
>>> print({'b': 1.0, 'a': 1.0})
{'a': 1.0, 'b': 1.0}
複製代碼

所以,Python的字典不能保證按照他們在BSON中的順序顯示鍵值對。下面的例子中,'a'顯示在'b'前面:

>>> print(collection.find_one())
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
複製代碼

使用SON類能夠在讀取BSON時保持順序,它是一個記住了鍵順序的字典。首先,獲取集合的句柄,經過配置使用SON代替字典:

>>> from bson import CodecOptions, SON
>>> opts = CodecOptions(document_class=SON)
>>> opts
CodecOptions(document_class=<class 'bson.son.SON'>,
             tz_aware=False,
             uuid_representation=PYTHON_LEGACY,
             unicode_decode_error_handler='strict',
             tzinfo=None)
>>> collection_son = collection.with_options(codec_options=opts)
複製代碼

如今,查詢結果中的文檔和副本都用SON對象表示:

>>> print(collection_son.find_one())
SON([(u'_id', 1.0), (u'subdocument', SON([(u'b', 1.0), (u'a', 1.0)]))])
複製代碼

副本中鍵順序與實際存儲的一致:'b'在'a'前面。

因爲字典中的鍵順序沒有定義,因此你沒法預測它如何序列化到BSON。但MongoDB認爲副本只有在他們的鍵具備相同的順序時纔是相同的。因此,使用字典查詢副本可能沒有結果:

>>> collection.find_one({'subdocument': {'a': 1.0, 'b': 1.0}}) is None
True
複製代碼

在查詢中交換鍵順序沒有任何區別:

>>> collection.find_one({'subdocument': {'b': 1.0, 'a': 1.0}}) is None
True
複製代碼

正如咱們上面看到的,Python認爲這兩個字典是相同的。

由兩個解決方法。第一個方法是按字段匹配副本:

>>> collection.find_one({'subdocument.a': 1.0,
...                      'subdocument.b': 1.0})
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
複製代碼

上面的查詢匹配任何'a'爲1.0和'b'爲1.0的副本,不管你在Python中指定它們的順序如何或它們存儲在BSON中的順序如何。 此外,此查詢如今能夠將副本中與'a'和'b'以外的其餘鍵相匹配,而以前的查詢須要徹底匹配。

第二個方法是使用SON來指定鍵的順序:

>>> query = {'subdocument': SON([('b', 1.0), ('a', 1.0)])}
>>> collection.find_one(query)
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
複製代碼

查詢時,在建立SON時使用的鍵順序在被序列化爲BSON時會被保留。所以,您能夠建立一個徹底匹配集合中的副本的副本。

更多信息,請參閱 MongoDB Manual entry on subdocument matching

CursorNotFound 遊標id無效在服務端是什麼意思?

若是MongoDB中的遊標已經打開了很長時間而沒有對它們執行任何操做,他們會在服務器上超時。這可能會致使在迭代遊標時引起CursorNotFound異常。

如何更改遊標的超時時間?

MongoDB不支持遊標自定義超時時間,但能夠徹底關閉。在find()時傳入no_cursor_timeout=True

如何存儲decimal.Decimal實例?

PyMongo >= 3.4 支持引入Decimal128 BSON類型。詳情請參閱Decimal12

MongoDB <= 3.2 僅支持IEEE 754 浮點數-與Python浮點類型相同。PyMongo能夠在這些版本的MongoDB中存儲Decimal實例的惟一方法是將他們轉換成這個標準,因此無論如何你只能夠存儲浮點數。咱們強迫用戶明確的作這個轉換來告知它們轉換正在發生。

我保存了9.99,可是查詢是確變成了9.9900000000000002,這是怎麼回事?

數據庫將9.99表示爲IEEE浮點數(這是MongoDB和Python以及大多數其餘現代語言通用的)。問題是9.99不能用雙精度浮點數來表示,在Python的某些版本中也是如此:

>>> 9.99
9.9900000000000002
複製代碼

使用PyMongo保存9.99時獲得的結果與使用JavaScript shell或任何其餘語言保存的結果徹底相同(以及將9.99輸入到 Python程序的同樣)。

大家能添加對文檔屬性方式的取值嗎?

經過.獲取文檔的值,而不只僅是如今只能用Python字典的方式獲取

這個請求已經出現了不少次,但咱們決定不實現任何這樣的方式。相關的jria case有關於這個決定的一些信息,這裏是一個簡短的總結:

  1. 這將污染文檔的屬性名稱空間,所以當使用與字典方法相同的名稱的鍵時可能致使細微的bugs/混淆錯誤。
  2. 咱們使用SON對象而不是常規字典的惟一緣由是維護鍵排序,由於服務器須要這些準確的操做。所以咱們在是否須要複雜的SON的必要性上猶豫不決(某種程度上咱們但願恢復單獨使用字典,而不會破壞每一個人的向後兼容性)
  3. 由於文檔的表現像字典,因此新用戶很容易(和Pythonic)的處理文檔。若是咱們開始改變這點將會爲新受增長一個障礙-另外的學習成本。

PyMongo中處理時區的正確方法是什麼?

有關如何正確處理datetime對象的示例,請參閱Datetime and Timezones

如何保存一個datetime.date實例?

PyMongo不支持保存datetime.date實例,由於BSON沒有類型來保存(沒有時間的日期(yyyy-MM-dd))。PyMongo並無強制將datetime.date轉換爲datetime.datetime的約定,因此你須要在代碼中執行轉換。

在web程序中使用ObjectId查詢文檔時,沒有獲得任何結果?

在Web應用程序中,一般在URL中會對文檔的ObjectId進行編碼,如:

"/posts/50b3bda58a02fb9a84d8991e"
複製代碼

web框架將ObjectId做爲url字符串的一部分傳遞給後臺,所以在她們呢傳遞給find_one()以前,必須轉換爲ObjectId。忘記這個轉換是一個常見的錯誤。下面的例子是在Flask中正確的執行操做(其餘Web框架相似):

rom pymongo import MongoClient
from bson.objectid import ObjectId

from flask import Flask, render_template

client = MongoClient()
app = Flask(__name__)

@app.route("/posts/<_id>")
def show_post(_id):
   # NOTE!: converting _id from string to ObjectId before passing to find_one
   post = client.db.posts.find_one({'_id': ObjectId(_id)})
   return render_template('post.html', post=post)

if __name__ == "__main__":
    app.run()
複製代碼

更多內容,請參閱Querying By ObjectId

如何在Django中使用PyMongo?

Django是一個流行的Python Web框架。 Django包含一個ORM,django.db。 目前,Django沒有官方的MongoDB庫。

django-mongodb-engine是一個非官方的MongoDB庫,支持Django聚合,(原子)更新,嵌入對象,Map / Reduce和GridFS。它容許您使用Django的大部份內置功能,包括ORM,admin,authentication,site 和會話框架以及緩存。

可是,在Django中不使用Django庫也很容易使用MongoDB和PyMongo。除了某些須要django.db(管理,認證和會話)的Django的功能不能使用MongoDB外,Django提供的大部分功能仍然可使用。

有一個讓Django和MongoDB容易使用的項目是mango,mango是爲Django會話和認證開發的一系列MongoDB庫(徹底繞過django.db)。

PyMongo是否可使用mod_wsgi?

能夠。詳情請參閱PyMongo and mod_wsgi

如何使用Python的json模塊來將個人文檔編碼爲JSON?

json_util是PyMongo內置的工具庫,能夠靈活與Python的json模塊與BSON文檔和 MongoDB Extended JSON一塊兒使用。因爲json模塊不支持一些PyMongo的特殊類型(好比ObjectId和DBRef),因此不能支持全部documents。

python-bsonjs是創建在libbson之上的將BSON快速轉換爲 MongoDB Extended JSON的轉換器。python-bsonjs不依賴於PyMongo,能夠提供比json_util更好的性能。python-bsonjs在使用RawBSONDocument時與PyMongo最適合。

在解碼另外一種語言存儲的日期時爲何會出現OverflowError?

PyMongo將BSON日期時間值解碼爲Python的datetime.datetime實例。datetime.datetime的實例被限制在datetime.MINYEAR(一般爲1)和datetime.MAXYEAR(一般爲9999)之間。某些MongoDB驅動程序(例如PHP驅動程序)能夠存儲遠遠超出datetime.datetime支持的年份值的BSON日期時間。

有幾種方法能夠解決此問題。 一種選擇是過濾掉datetime.datetime支持的範圍之外的值的文檔:

>>> from datetime import datetime
>>> coll = client.test.dates
>>> cur = coll.find({'dt': {'$gte': datetime.min, '$lte': datetime.max}})
複製代碼

另外一個方法是在你不須要日期時間字段時過濾掉這個字段:

>>> cur = coll.find({}, projection={'dt': False})
複製代碼

在多進程中使用PyMongo

在Unix系統上,多進程模塊使用fork()生成進程。在fork()中使用MongoClient實例時必須當心:MongoClient的實例不能從父進程複製到子進程,父進程和每一個子進程必須建立他們本身的MongoClient實例。例如:

# Each process creates its own instance of MongoClient.
def func():
    db = pymongo.MongoClient().mydb
    # Do something with db.

proc = multiprocessing.Process(target=func)
proc.start()
複製代碼

永遠不要這樣作:

client = pymongo.MongoClient()

# Each child process attempts to copy a global MongoClient
# created in the parent process. Never do this.
def func():
  db = client.mydb
  # Do something with db.

proc = multiprocessing.Process(target=func)
proc.start()
複製代碼

因爲fork()、線程和鎖之間固有的不兼容性,從父進程複製的MongoClient實例在子進程中死鎖的可能性很高。

相關文章
相關標籤/搜索