我們選用 MongoDB 作為網(wǎng)站的數(shù)據(jù)庫系統(tǒng),它是一個開源的 NoSQL 數(shù)據(jù)庫,相比MySQL 那樣的關系型數(shù)據(jù)庫,它更為輕巧、靈活,非常適合在數(shù)據(jù)規(guī)模很大、事務性不強的場合下使用。
一、NoSQL
參考
NoSQL 還是 SQL ?這一篇講清楚
NoSQL經(jīng)典詳解
什么是 NoSQL 呢?為了解釋清楚,首先讓我們來介紹幾個概念。在傳統(tǒng)的數(shù)據(jù)庫中,數(shù)據(jù)庫的格式是由表(table)、行(row)、字段(field)組成的。表有固定的結構,規(guī)定了每行有哪些字段,在創(chuàng)建時被定義,之后修改很困難。行的格式是相同的,由若干個固定的字段組成。每個表可能有若干個字段作為索引(index),這其中有的是主鍵(primary key),用于約束表中的數(shù)據(jù),還有唯一鍵(unique key),確保字段中不存放重復數(shù)據(jù)。表和表之間可能還有相互的約束,稱為外鍵(foreign key)。對數(shù)據(jù)庫的每次查詢都要以行為單位,復雜的查詢包括嵌套查詢、連接查詢和交叉表查詢。擁有這些功能的數(shù)據(jù)庫被稱為關系型數(shù)據(jù)庫,關系型數(shù)據(jù)庫通常使用一種叫做 SQL(Structured Query Language)的查詢語言作為接口,因此又稱為 SQL 數(shù)據(jù)庫。典型的 SQL 數(shù)據(jù)庫有 MySQL、Oracle、Microsoft SQL Server、PostgreSQL、SQLite,等等。
NoSQL 是 1998 年被提出的,它曾經(jīng)是一個輕量、開源、不提供SQL功能的關系數(shù)據(jù)庫。但現(xiàn)在 NoSQL 被認為是 Not Only SQL 的簡稱,主要指非關系型、分布式、不提供 ACID 的數(shù)據(jù)庫系統(tǒng)。正如它的名稱所暗示的,NoSQL 設計初衷并不是為了取代 SQL 數(shù)據(jù)庫的,而是作為一個補充,它和 SQL 數(shù)據(jù)庫有著各自不同的適應領域。NoSQL 不像 SQL 數(shù)據(jù)庫一樣都有著統(tǒng)一的架構和接口,不同的 NoSQL 數(shù)據(jù)庫系統(tǒng)從里到外可能完全不同。
各個數(shù)據(jù)之間存在關聯(lián)是關系型數(shù)據(jù)庫得名的主要原因,為了進行join處理,關系型數(shù)據(jù)庫不得不把數(shù)據(jù)存儲在同一個服務器內(nèi),這不利于數(shù)據(jù)的分散,這也是關系型數(shù)據(jù)庫并不擅長大數(shù)據(jù)量的寫入處理的原因。相反NoSQL數(shù)據(jù)庫原本就不支持Join處理,各個數(shù)據(jù)都是獨立設計的,很容易把數(shù)據(jù)分散在多個服務器上,故減少了每個服務器上的數(shù)據(jù)量,即使要處理大量數(shù)據(jù)的寫入,也變得更加容易,數(shù)據(jù)的讀入操作也很容易。例如:谷歌和Facebook每天為他們的用戶收集萬億比特的數(shù)據(jù)。這些數(shù)據(jù)的存儲不需要固定的模式,無需多余的操作就可以橫向擴展。

1.HBase(列存儲)
兩大用途:
- 特別適用于簡單數(shù)據(jù)寫入(如“消息類”應用)和海量、結構簡單數(shù)據(jù)的查詢(如“詳單類”應用)。特別地,適合稀疏表。(個人覺得存?zhèn)€網(wǎng)頁內(nèi)容是極好極好的)
- 作為MapReduce的后臺數(shù)據(jù)源,以支撐離線分析型應用。
場景:Facebook的消息類應用,包括Messages、Chats、Emails和SMS系統(tǒng),用的都是HBase;淘寶的WEB版阿里旺旺,后臺是HBase;小米的米聊用的也是HBase;移動某省公司的手機詳單查詢系統(tǒng)。(單次分析,只能scan全表或者一個范圍內(nèi)的)
2.MongoDB
- 是一個介于關系型和非關系型之間的一個產(chǎn)品吧,類SQL語言,支持索引
- MongoDb在類SQL語句操作方面目前比HBase具備更多一些優(yōu)勢,有二級索引,支持相比于HBase更復雜的集合查找等。
- BSON的數(shù)據(jù)結構使得處理文檔型數(shù)據(jù)更為直接。支持復雜的數(shù)據(jù)結構
- MongoDb也支持mapreduce,但由于HBase跟Hadoop的結合更為緊密,Mongo在數(shù)據(jù)分片等mapreduce必須的屬性上不如HBase這么直接,需要額外處理。
3.RedisRedis
- 為內(nèi)存型KV系統(tǒng),處理的數(shù)據(jù)量要小于HBase與MongoDB
- Redis很適合用來做緩存,但除此之外,它實際上還可以在一些“讀寫分離”的場景下作為“讀庫”來用,特別是用來存放Hadoop或Spark的分析結果。
- Redis的讀寫性能在100,000ops/s左右,時延一般為10~70微妙左右;而HBase的單機讀寫性能一般不會超過1,000ops/s,時延則在1~5毫秒之間。
- Redis的魅力還在于它不像HBase只支持簡單的字符串,他還支持集合set,有序集合zset和哈希hash
二、知乎 NoSql是一種語言,還是一種概念?
空口說比較無趣,我舉一個現(xiàn)實的例子。我曾就職的一個公司有一個系統(tǒng)要是對用戶給出推薦的廣告。流程很簡單,根據(jù)cookie查找用戶屬性,然后有專門的系統(tǒng)給出推薦列表。
我們只討論這個根據(jù)cookie查找用戶屬性。這個功能用sql可以做嗎?當然可以,但是為什么要用sql?
- 1、這個特性永遠永遠是1對1的查找,不需要任何sum,count等功能;
- 2、這個特性永遠永遠是從cookie搜索屬性,不需要根據(jù)屬性來查找,所以where也用不上(更別說join了);
- 3、數(shù)據(jù)只有一個更新者,基于hadoop的機器學習程序,所以鎖也不是必須的;
- 4、這個業(yè)務甚至不介意臟數(shù)據(jù),如果因為同步等原因造成讀到的事舊的屬性,也不過就是拿昨天的數(shù)據(jù)給他推廣告罷了。
這個業(yè)務真正關心的是什么?
- 時延一定要小,上游對整個廣告過程只給了500ms,你還需要去競價,需要去選擇廣告,還要預留網(wǎng)絡時延。
- 數(shù)據(jù)量大,中國的網(wǎng)民數(shù)量是比較多的,幾億條記錄是下限。
- 并發(fā)要高,每當網(wǎng)民打開一個網(wǎng)頁,有幾個廣告位就會發(fā)生幾次廣告競價。
- 最后,很不幸這個業(yè)務毛利率很低,要是在軟件或者硬件是花費過多有可能虧本的。
所以這個特性我相信業(yè)界都是使用key-value數(shù)據(jù)庫,redis放得下就用redis(以前沒有集群,單機放不下得想其他辦法)。在拋棄掉sql不必要的束縛后,kv數(shù)據(jù)庫可以在同樣的軟硬件成本下,實現(xiàn)更低的時延和更高的吞吐量。
我的高中數(shù)學老師曾經(jīng)說過一句話,“你一定要掌握一個問題的通解,因為在考試的時候你無法保證可以在有限的時間內(nèi)找到特解”;而我的大學老師(好像是信號處理)說過另外一句話“如果你沒有找到某個問題的特解,那么說明你還不夠了解這個問題”。
sql是一個很好的通解,這個框里面你可以放任何東西,而且它的一切都是可以預期的。但是當行業(yè)不斷發(fā)展了之后,大家開始對業(yè)務上的一些問題了解越來越深入,而且這些業(yè)務的量也大到了你愿意單獨為它搭一套系統(tǒng)。此時,不同的nosql作為不同特定問題的特解出現(xiàn)就自然而然了。所以nosql不可能取代sql,但它本身的發(fā)展也是不可逆的。另外給一個建議,如果你的系統(tǒng)訪問量用一個單機mysql就可以搞定,那么還是繼續(xù)用mysql吧,別本末倒置。
三、MongoDB簡介
參考
知乎 怎樣學 MongoDB?
MongoDB 極簡實踐入門
MongoDB 是一個對象數(shù)據(jù)庫,它沒有表、行等概念,也沒有固定的模式和結構,所有的數(shù)據(jù)以文檔的形式存儲。所謂文檔就是一個關聯(lián)數(shù)組式的對象,它的內(nèi)部由屬性組成,一個屬性對應的值可能是一個數(shù)、字符串、日期、數(shù)組,甚至是一個嵌套的文檔。下面是一個MongoDB 文檔的示例:
{ "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),
"uid" : 2004,
"username" : "byvoid",
"net9" : { "nickname" : "BYVoid",
"surname" : "Kuo",
"givenname" : "Carbo",
"fullname" : "Carbo Kuo",
"emails" : [ "byvoid@byvoid.com", "byvoid.kcp@gmail.com" ],
"website" : "http://www.byvoid.com",
"address" : "Zijing 2#, Tsinghua University" }
}
上面文檔中 uid 是一個整數(shù)屬性, username 是字符串屬性, _id 是文檔對象的標識符,格式為特定的 ObjectId 。 net9 是一個嵌套的文檔,其內(nèi)部結構與一般文檔無異。從格式來看文檔好像 JSON,沒錯,MongoDB 的數(shù)據(jù)格式就是 JSON,因此與 JavaScript 的親和性很強。在 Mongodb 中對數(shù)據(jù)的操作都是以文檔為單位的,當然我們也可以修改文檔的部分屬性。對于查詢操作,我們只需要指定文檔的任何一個屬性,就可在數(shù)據(jù)庫中將滿足條件的所有文檔篩選出來。為了加快查詢,MongoDB 也對文檔實現(xiàn)了索引,這一點和 SQL 數(shù)據(jù)庫一樣。
四、MongoDB安裝
先是看了一下windows下MongoDB的安裝及配置,然后自己下載了安裝包,才發(fā)現(xiàn)是4.0.9版本的,差別比較大。比較重要一點是:
從 MongoDB 4.0 開始,默認情況下,你可以在安裝期間配置和啟動 MongoDB 作為服務,并在成功安裝后啟動 MongoDB 服務。也就是說,MongoDB 4.0 已經(jīng)不需要像以前版本那樣輸入一堆命令行來將 MongoDB 配置成 Windows 服務來自動運行了,方便了很多。
Win10 安裝配置 MongoDB 4.0 踩坑記
在3中的許多配置(如 設置dbpath、logpath、安裝服務等),在4中都可以省去。??也就是說,在MongoDB4.0.0中,只要安裝好了,基本不用配置就可以用了。由于之前不知道這些,而且安裝配置的教程都是參照MongoDB3的,所以走了許多彎路。
參考如下,開始安裝
Mongodb最新版本安裝(4.0以上)
mongoDB的使用學習(一)mongoDB4.0.6的下載安裝配置
1.設置service name和配置路徑

如果你選擇不將 MongoDB 配置為服務,請取消選中 Install MongoD as a Service。
-
如果你選擇將 MongoDB 配置為服務,則可以:
- 指定以下列用戶之一運行服務:
- 網(wǎng)絡服務用戶;即 Windows 內(nèi)置的 Windows 用戶帳戶
- 本地或域用戶:
- 對于現(xiàn)有本地用戶帳戶,Account Domain 指定為 .,并為該用戶指定 Account Name 和 Account Password。
- 對于現(xiàn)有域用戶,請為該用戶指定 Account Domain,Account Name 和 Account Password。
- 指定以下列用戶之一運行服務:
-
指定 Service Name。如果你已擁有具有指定名稱的服務,則必須選擇其他名稱。
- 指定 Data Directory(數(shù)據(jù)保存目錄),對應于 --dbpath。如果該目錄不存在,安裝程序將創(chuàng)建該目錄并為服務用戶設置訪問權限。
- 指定 Log Directory(日志保存目錄),該目錄對應于 --logpath。如果該目錄不存在,安裝程序將創(chuàng)建該目錄并為服務用戶設置訪問權限。
2.MongoDB Compass是個可視化工具,如果勾選安裝的話是在線下載安裝的,據(jù)說有的人安裝一整晚都沒裝好就是因為這個。我這里是取消勾選的,因為之后我會裝別的工具使用

3.簡單使用
我這里全部默認設置,一路next,然后點完finish,就沒了……
呃,只能自己打開cmd,然后在C:\Program Files\MongoDB\Server\4.0\bin路徑下(MongoDB配置環(huán)境變量,可以任意路徑運行mongo命令
)運行mongo命令發(fā)現(xiàn)還是安裝成功了。在這個路徑下打開mongod.cfg,能看到配置,比如data和log的位置。使用瀏覽器打開http://127.0.0.1:27017/:It looks like you are trying to access MongoDB over HTTP on the native driver port.
使用show dbs看一下:
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
> show collections
movie
>
show dbs 顯示所有數(shù)據(jù)庫的名稱和存儲情況
use xxx 轉到xxx數(shù)據(jù)庫,如果沒有該數(shù)據(jù)庫,那就建立該數(shù)據(jù)庫
db.createCollection('author')創(chuàng)建集合;我們試著往我們的數(shù)據(jù)庫里添加一個集合(collection),MongoDB里的集合和SQL里面的表格是類似的
更多命令參考
https://www.mongodb.org.cn/tutorial/
http://www.runoob.com/mongodb/mongodb-tutorial.html
4.可視化工具
如果你是Mongo的企業(yè)版用戶,不如嘗試MongoDB的官方GUI:MongoDB Compass
我最終選了RoboMongo,現(xiàn)在已經(jīng)改名為robo 3t。以下參考MongoDB可視化工具--Robo 3T 使用教程

下載后一路next,然后默認連接就能看到數(shù)據(jù)了。
五、在go中使用
1. Mgo 驅動
地址: https://godoc.org/labix.org/v2/mgo或https://github.com/go-mgo/mgo
地址: https://github.com/globalsign/mgo
文檔: https://godoc.org/github.com/globalsign/mgo
說明:上面第一個地址,是 mgo 的原地址,目前作者已經(jīng)停止維護。第二個地址是基于原作者的社區(qū)維護版本,也是作者推薦的方案之一。
2. 官方驅動
地址: https://github.com/mongodb/mongo-go-driver
文檔:https://godoc.org/github.com/mongodb/mongo-go-driver/mongo
參考:
MongoDB官方推出的Go驅動庫“mongo-go-driver”快速教程
截止到2019.5.24,官方驅動達到2648 star,已經(jīng)超過mgo驅動了,建議使用官方版本。
3.使用Mgo 驅動實例
參考
Go實戰(zhàn)--golang中使用MongoDB(mgo)
三、go語言操作 mongodb mgo --go語言學習筆記
package main
import (
"fmt"
"log"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Name string
Phone string
}
func main() {
//連接
session, err := mgo.Dial("localhost:27017")
if err != nil {
panic(err)
}
defer session.Close()
// Optional. Switch the session to a monotonic behavior.
session.SetMode(mgo.Monotonic, true)
//通過Database.C()方法切換集合(Collection)
//func (db Database) C(name string) *Collection
c := session.DB("test").C("people")
//插入
//func (c *Collection) Insert(docs ...interface{}) error
err = c.Insert(&Person{"superWang", "13478808311"},
&Person{"David", "15040268074"})
if err != nil {
log.Fatal(err)
}
result := Person{}
//查詢
//func (c Collection) Find(query interface{}) Query
err = c.Find(bson.M{"name": "superWang"}).One(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println("Name:", result.Name)
fmt.Println("Phone:", result.Phone)
}
----------------------
Name: superWang
Phone: 13478808311
可以看到上面的Go文件插入了兩個數(shù)據(jù),在Cmd里可以驗證下(關于查詢,參考MongoDB系列一(查詢).):
> use test
switched to db test
> show collections
people
> db.people.find()
{ "_id" : ObjectId("5ce799b3c6bfff8dd9776a25"), "name" : "superWang", "phone" : "13478808311" }
{ "_id" : ObjectId("5ce799b3c6bfff8dd9776a26"), "name" : "David", "phone" : "15040268074" }
db.people.find({name:'superWang'})
{ "_id" : ObjectId("5ce799b3c6bfff8dd9776a25"), "name" : "superWang", "phone" : "13478808311" }

(1)插入
注意insert插入的Person結構體,自動變成小寫屬性寫入了mongo。再看一個例子:
type User struct {
Id_ bson.ObjectId `bson:"_id"`
Name string `bson:"name"`
Age int `bson:"age"`
JoinedAt time.Time `bson:"joined_at"`
Interests []string `bson:"interests"`
}
通過bson:”name”這種方式(也可以省略bson部分,只寫"name")可以定義MongoDB中集合的字段名,如果不定義,mgo自動把struct的字段名首字母小寫作為集合的字段名。如果不需要獲得id_,Id_可以不定義,在插入的時候會自動生成。
err = c.Insert(&User{
Id_: bson.NewObjectId(),
Name: "Jimmy Kuu",
Age: 33,
JoinedAt: time.Now(),
Interests: []string{"Develop", "Movie"},
})
上面可以插入自己生成id的數(shù)據(jù),注意如果沒有bson:"_id"這個標記,數(shù)據(jù)里會出現(xiàn)id_和_id兩個屬性,因為mongo直接把"Id_"變成小寫的"id_"了。這里通過bson.NewObjectId()來創(chuàng)建新的ObjectId,如果創(chuàng)建完需要用到的話,放在一個變量中即可,一般在Web開發(fā)中可以作為參數(shù)跳轉到其他頁面。
注:插入也可以使用c.Insert(bson.M{"name":"cuixx"})這種方式。
(2)查詢
通過func (c *Collection) Find(query interface{}) *Query來進行查詢,返回的Query struct可以有附加各種條件來進行過濾。通過Query.All()可以獲得所有結果,通過Query.One()可以獲得一個結果,注意如果沒有數(shù)據(jù)或者數(shù)量超過一個,One()會報錯。條件用bson.M{key: value},注意key必須用MongoDB中的字段名,而不是struct的字段名。
//可以通過id來查詢
id := "5204af979955496907000001"
objectId := bson.ObjectIdHex(id)
user := new(User)
c.Find(bson.M{"_id": objectId}).One(&user)
//更簡單的方式是直接用FindId()方法:
c.FindId(objectId).One(&user)
(3)更新
c.Update(bson.M{"_id":objectId},bson.M{"$set":bson.M{"name":"cuixu"}}),注意修改單個或多個字段需要通過$set操作符號,否則集合會被替換。
(4)字段增加值
c.Update(bson.M{"_id": objectId,
bson.M{"$inc": bson.M{
"age": -1,
}})
(5)從數(shù)組中增加一個元素
c.Update(bson.M{"_id": objectId,
bson.M{"$push": bson.M{
"interests": "Golang",
}})
(6)從數(shù)組中刪除一個元素
c.Update(bson.M{"_id": objectId,
bson.M{"$pull": bson.M{
"interests": "Golang",
}})
(7)刪除
c.Remove(bson.M{"name": "Jimmy Kuu"})
(8)Upsert,UpsertId
如果數(shù)據(jù)存在就更新,否則就新增一條記錄:func (c *Collection) Upsert(selector interface{}, update interface{}) (info *ChangeInfo, err error)
selector := bson.M{"key": "max"}
data := bson.M{"$set": bson.M{"value": 30}}
changeInfo, err := getDB().C("config").Upsert(selector, data)
還有func (c *Collection) UpsertId(id interface{}, update interface{}) (info *ChangeInfo, err error)也是類似的,確定根據(jù)ID來查找,比如info, err := collection.Upsert(bson.M{"_id": id}, update)