在這篇文章中,將向您展示如何使用Python鏈接目前主流的MongoDB(V3.4.0)數據庫,主要使用PyMongo(v3.4.0)和MongoEngine(V0.10.7)。同時比較SQL和NoSQL。
1、SQL vs NoSQL
如果你不是很熟悉NoSQL這個概念,MongoDB就是一個NoSQL數據庫。近幾年來它越來越受到整個行業的歡迎。NoSQL數據庫提供了一個和關系型數據庫非常不同的檢索方式和存儲數據功能。
在NoSQL出現的幾十年來,SQL數據庫是開發者尋求構建大型、可擴展系統的唯一選擇之一。然而,越來越多的需求要求存儲復雜數據結構的能力。這推動了NoSQL數據庫的誕生,它允許開發者存儲異構和無結構的數據。
當到數據庫方案選擇時,大多數人都問自己最后一個問題,“SQL或NoSQL的?”。無論是SQL和NoSQL都有自己的長處和弱點,你應該選擇適合您的應用需求中最好的之一。這里是兩者之間的一些區別:
SQL
- 模型是關系型的;
- 數據被存放在表中;
- 適用於每條記錄都是相同類型並具有相同屬性的情況;
- 存儲規范需要預定義結構;
- 添加新的屬性意味着你必須改變整體架構;
- ACID事務支持;
NoSQL
- 模型是非關系型的;
- 可以存儲Json、鍵值對等(決定於NoSQL數據庫類型);
- 並不是每條記錄都要有相同的結構;
- 添加帶有新屬性的數據時,不會影響其他;
- 支持ACID事務,根據使用的NoSQL的數據庫而有所不同;
- 一致性可以改變;
- 橫向擴展;
在兩種類型的數據庫之間還有許多其他的區別,但上面提到的是一些更重要的區別。根據您的具體情況,使用SQL數據庫可能是首選,而在其他情況下,NoSQL的是更明顯的選擇。當選擇一個數據庫時,您應該謹慎考慮每個數據庫的優勢和劣勢。
NoSQL的一個好處是,有許多不同類型的數據庫可供選擇,並且每個都有自己的用例:
還有很多,但這些是一些更常見的類型。近年來,SQL和NoSQL數據庫甚至已經開始合並。例如,PostgreSQL現在支持存儲和查詢JSON數據,很像MongoDB。有了這個,你可以用Postgres實現MongoDB一樣的功能,但你仍然沒有MongoDB的其他優勢(如橫向擴容和簡單的界面,等等)。
2、MongoDB
現在,讓我們將視線轉移到本文的重點,並闡明的MongoDB的具體的一些情況。
MongoDB是一個面向文檔的,開源數據庫程序,它平台無關。MongoDB像其他一些NoSQL數據庫(但不是全部!)使用JSON結構的文檔存儲數據。這是使得數據非常靈活,不需要的Schema。
一些比較重要的特點是:
- 支持多種標准查詢類型,比如matching()、comparison (, )或者正則表達式;
- 可以存儲幾乎任何類型的數據,無論是結構化,部分結構化,甚至是多態;
- 要擴展和處理更多查詢,只需添加更多的機器;
- 它是高度靈活和敏捷,讓您能夠快速開發應用程序;
- 作為基於文檔的數據庫意味着您可以在單個文檔中存儲有關您的模型的所有信息;
- 您可以隨時更改數據庫的Schema;
- 許多關系型數據庫的功能也可以在MongoDB使用(如索引)。
在運行方面,MongoDB中有相當多的功能在其他數據庫中是沒有的:
- 無論您需要獨立服務器還是完整的獨立服務器集群,MongoDB都可以根據需要進行擴展;
- MongoDB還通過在各個分片上自動移動數據來提供負載均衡支持;
- 它具有自動故障轉移支持,如果主服務器Down掉,新的主服務器將自動啟動並運行;
- MongoDB的管理服務(MMS)可以用於監控和備份MongoDB的基礎設施服務;
- 不像關系數據庫,由於內存映射文件,你將節省相當多的RAM。
雖然起初MongoDB似乎是解決我們許多問題的數據庫,但它不是沒有缺點的。MongoDB的一個常見缺點是缺少對ACID事務的支持,MongoDB在特定場景下支持ACID事務,但不是在所有情況。在單文檔級別,支持ACID事務(這是大多數事務發生的地方)。但是,由於MongoDB的分布式性質,不支持處理多個文檔的事務。
MongoDB還缺少對自然join查詢支持。在MongoDB看來:文檔意在包羅萬象,這意味着,一般來說,它們不需要參考其他文檔。在現實世界中,這並不總是有效的,因為我們使用的數據是關系性的。因此,許多人認為MongoDB應該被用作一個SQL數據庫的補充數據庫,但是當你使用MongoDB是,你會發現這是錯誤的
3、PyMongo
現在我們已經描述了MongoDB的是什么,讓我們來看看如何在Python中實際使用它。由MongoDB開發者發布的官方驅動程序PyMongo,這里通過一些例子介紹,但你也應該查看完整的文檔,因為我們無法面面俱到。
當然第一件事就是安裝,最簡單的方式就是pip
:
1
|
pip install pymongo==3.4.0
|
注:有關更全面的指南,請查看文檔的安裝/升級頁面,並按照其中的步驟進行設置
完成設置后,啟動的Python控制台並運行以下命令:
1
|
>>> import pymongo
|
如果沒有提出任何異常就說明安裝成功了
建立連接
使用MongoClient
對象建立連接:
1
2
|
from pymongo import MongoClient
client = MongoClient()
|
使用上面的代碼片段,將建立連接到默認主機(localhost)和端口(27017)。您還可以指定主機和/或使用端口:
1
|
client = MongoClient('localhost', 27017)
|
或者使用MongoURl格式:
1
|
client = MongoClient('mongodb://localhost:27017')
|
訪問數據庫
一旦你有一個連接的MongoClient
實例,你可以在Mongo服務器中訪問任何數據庫。如果要訪問一個數據庫,你可以當作屬性一樣訪問:
1
|
db = client.pymongo_test
|
或者你也可以使用字典形式的訪問:
1
|
db = client['pymongo_test']
|
如果您的指定數據庫已創建,實際上並不重要。通過指定此數據庫名稱並將數據保存到其中,您將自動創建數據庫。
插入文檔
在數據庫中存儲數據,就如同調用只是兩行代碼一樣容易。第一行指定你將使用哪個集合。在MongoDB中術語中,一個集合是在數據庫中存儲在一起的一組文檔(相當於SQL的表)。集合和文檔類似於SQL表和行。第二行是使用集合插入數據insert_one()的方法:
1
2
3
4
5
6
7
8
|
posts = db.posts
post_data = {
'title': 'Python and MongoDB',
'content': 'PyMongo is fun, you guys',
'author': 'Scott'
}
result = posts.insert_one(post_data)
print('One post: {0}'.format(result.inserted_id))
|
我們甚至可以使用insert_one()同時插入很多文檔,如果你有很多的文檔添加到數據庫中,可以使用方法insert_many()。此方法接受一個list參數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
post_1 = {
'title': 'Python and MongoDB',
'content': 'PyMongo is fun, you guys',
'author': 'Scott'
}
post_2 = {
'title': 'Virtual Environments',
'content': 'Use virtual environments, you guys',
'author': 'Scott'
}
post_3 = {
'title': 'Learning Python',
'content': 'Learn Python, it is easy',
'author': 'Bill'
}
new_result = posts.insert_many([post_1, post_2, post_3])
print('Multiple posts: {0}'.format(new_result.inserted_ids))
|
你應該看到類似輸出:
1
2
3
4
5
6
|
One post: 584d947dea542a13e9ec7ae6
Multiple posts: [
ObjectId('584d947dea542a13e9ec7ae7'),
ObjectId('584d947dea542a13e9ec7ae8'),
ObjectId('584d947dea542a13e9ec7ae9')
]
|
注意: 不要擔心,你和上面顯示不一樣。它們是在插入數據時,由Unix的紀元,機器標識符和其他唯一數據組成的動態標識。
檢索文檔
檢索文檔可以使用find_one()方法,比如要找到author為Bill的記錄:
1
2
3
4
5
6
7
8
9
10
|
bills_post = posts.find_one({'author': 'Bill'})
print(bills_post)
運行結果
:
{
'author': 'Bill',
'title': 'Learning Python',
'content': 'Learn Python, it is easy',
'_id': ObjectId('584c4afdea542a766d254241')
}
|
您可能已經注意到,這篇文章的ObjectId是設置的_id,這是以后可以使用唯一標識。如果需要查詢多條記錄可以使用find()方法:
1
2
3
4
5
|
scotts_posts = posts.find({'author': 'Scott'})
print(scotts_posts)
結果
:
<pymongo.cursor.Cursor object at 0x109852f98>
|
他的主要區別在於文檔數據不是作為數組直接返回給我們。相反,我們得到一個游標對象的實例。這Cursor是一個包含相當多的輔助方法,以幫助您處理數據的迭代對象。要獲得每個文檔,只需遍歷結果:
1
2
|
for post in scotts_posts:
print(post)
|
4、MongoEngine
雖然PyMongo是非常容易使用,總體上是一個偉大的輪子,但是許多項目使用它都可能太低水平。簡而言之,你必須編寫很多自己的代碼來持續地保存,檢索和刪除對象。PyMongo之上提供了一個更高的抽象一個庫是MongoEngine。MongoEngine是一個對象文檔映射器(ODM),它大致相當於一個基於SQL的對象關系映射器(ORM)。MongoEngine提供的抽象是基於類的,所以你創建的所有模型都是類。雖然有相當多的Python的庫可以幫助您使用MongoDB,MongoEngine是一個更好的,因為它有一個很好的組合的功能,靈活性和社區支持。
使用pip安裝:
1
|
pip install mongoengine==0.10.7
|
連接:
1
2
|
from mongoengine import *
connect('mongoengine_test', host='localhost', port=27017)
|
和pymongo不同。MongoEngine需要制定數據庫名稱。
定義文檔
建立文檔之前,需要定義文檔中要存放數據的字段。與許多其他ORM類似,我們將通過繼承Document類,並提供我們想要的數據類型來做到這一點:
1
2
3
4
5
6
7
|
import datetime
class Post(Document):
title = StringField(required=True, max_length=200)
content = StringField(required=True)
author = StringField(required=True, max_length=50)
published = DateTimeField(default=datetime.datetime.now)
|
在這個簡單的模型中,我們已經告訴MongoEngine,我們的Post實例有title、content、author、published。現在Document對象可以使用該信息來驗證我們提供它的數據。
因此,如果我們試圖保存Post的中沒有title那么它會拋出一個Exception,讓我們知道。我們甚至可以進一步利用這個並添加更多的限制:
- required:設置必須;
- default:如果沒有其他值給出使用指定的默認值
- unique:確保集合中沒有其他document有此字段的值相同
- choices:確保該字段的值等於數組中的給定值之一
保存文檔
將文檔保存到數據庫中,我們將使用save()的方法。如果文檔中的數據庫已經存在,則所有的更改將在原子水平上對現有的文檔進行。如果它不存在,但是,那么它會被創建。
這里是創建和保存一個文檔的例子:
1
2
3
4
5
6
7
8
9
10
|
post_1 = Post(
title='Sample Post',
content='Some engaging content',
author='Scott'
)
post_1.save() # This will perform an insert
print(post_1.title)
post_1.title = 'A Better Post Title'
post_1.save() # This will perform an atomic edit on "title"
print(post_1.title)
|
調用save()的時候需要注意幾點:
- PyMongo將在您調用.save()時執行驗證,這意味着它將根據您在類中聲明的模式檢查要保存的數據,如果違反模式(或約束),則拋出異常並且不保存數據;
- 由於Mongo不支持真正的事務,因此沒有辦法像在SQL數據庫中那樣“回滾”.save()調用。
當你保存的數據沒有title時:
1
2
3
4
5
6
|
post_2 = Post(content='Content goes here', author='Michael')
post_2.save()
raise ValidationError(message, errors=errors)
mongoengine.errors.ValidationError:
ValidationError (Post:None) (Field is required: ['title'])
|
向對象的特性
使用MongoEngine是面向對象的,你也可以添加方法到你的子類文檔。例如下面的示例,其中函數用於修改默認查詢集(返回集合的所有對象)。通過使用它,我們可以對類應用默認過濾器,並只獲取所需的對象
1
2
3
4
5
6
7
|
class Post(Document):
title = StringField()
published = BooleanField()
@queryset_manager
def live_posts(clazz, queryset):
return queryset.filter(published=True)
|
關聯其他文檔
您還可以使用ReferenceField對象來創建從一個文檔到另一個文檔的引用。MongoEngine在訪問時自動惰性處理引用。
1
2
3
4
5
6
7
|
class Author(Document):
name = StringField()
class Post(Document):
author = ReferenceField(Author)
Post.objects.first().author.name
|
在上面的代碼中,使用文檔”外鍵”,我們可以很容易地找到第一篇文章的作者。其實還有比這里介紹的更多的字段類(和參數),所以一定要查看文檔字段更多信息。
從所有這些示例中,您應該能夠看到,MongoEngine非常適合管理幾乎任何類型的應用程序的數據庫對象。這些功能使得創建一個高效可擴展程序變得非常容易。如果你正在尋找更多關於MongoEngine的幫助,請務必查閱他們的用戶指南。