瞭解 MongoDB 看這一篇就夠了
作者:美碼師
一、簡介
MongoDB 是一款流行的開源文件型資料庫,從它的命名來看,確實是有一定野心的。MongoDB 的原名一開始來自於 英文單詞"Humongous", 中文含義是指"龐大",即命名者的意圖是可以處理大規模的資料。
但筆者更喜歡稱呼它為 "芒果"資料庫,除了譯音更加相近之外,原因還來自於這幾年使用 MongoDB 的兩層感覺:
第一層感受是"爽",使用這個文件資料庫的特點是幾乎不受什麼限制,一方面Json文件式的結構更容易理解,而無Schema約束也讓DDL管理更加簡單,一切都可以很快速的進行。
第二層感受是"酸爽",這點相信幹運維或是支撐性工作的兄弟感受會比較深刻,MongoDB 由於入門體驗"太過於友好",導致一些團隊認為用好這個資料庫是個很簡單的事情,所以開發兄弟在存量系統上埋一些坑也是正常的事情。所謂交付一時爽,維護火葬場.. 當然了,這句話可能有些過。 但這裡的潛臺詞是:與傳統的RDBMS資料庫一樣,MongoDB 在使用上也需要認真的考量和看護,不然的話,會遇到更多的坑。
那麼,儘管文件資料庫在選型上會讓一些團隊望而卻步,仍然不阻礙該資料庫所獲得的一些支援,比如 DB-Engine 上的排名:
圖-DBEngine排名
在全部的排名中,MongoDB 長期排在第5位(文件資料庫排名第1位),同時也是最受歡迎的 NoSQL 資料庫。另外,MongoDB 的社群一直比較活躍,加上商業上的驅動(MongoDB於2017年在納斯達克上市),這些因素都推動了該開源資料庫的發展。
如果對於 MongoDB的發展史感興趣,可以參考下沒有一個技術天生完美,MongoDB 十年發展全紀錄這篇文章。
MongoDB 資料庫的一些特性:
面向文件儲存,基於JSON/BSON 可表示靈活的資料結構
動態 DDL能力,沒有強Schema約束,支援快速迭代
高效能運算,提供基於記憶體的快速資料查詢
容易擴充套件,利用資料分片可以支援海量資料儲存
豐富的功能集,支援二級索引、強大的聚合管道功能,為開發者量身定做的功能,如資料自動老化、固定集合等等。
跨平臺版本、支援多語言SDK..
假定你是初次瞭解 MongoDB,下面的內容將能幫助你對該資料庫技術的全貌產生一定的瞭解。
二、基本模型
資料結構對於一個軟體來說是至關重要的,MongoDB 在概念模型上參考了 SQL資料庫,但並非完全相同。
關於這點,也有人說,MongoDB 是 NoSQL中最像SQL的資料庫..
如下表所示:
SQL概念 | MongoDB概念 |
---|---|
database | database |
table | collection |
row | document |
column | field |
說明
database 資料庫,與SQL的資料庫(database)概念相同,一個資料庫包含多個集合(表)
collection 集合,相當於SQL中的表(table),一個集合可以存放多個文件(行)。不同之處就在於集合的結構(schema)是動態的,不需要預先宣告一個嚴格的表結構。更重要的是,預設情況下 MongoDB 並不會對寫入的資料做任何schema的校驗。
document 文件,相當於SQL中的行(row),一個文件由多個欄位(列)組成,並採用bson(json)格式表示。
field 欄位,相當於SQL中的列(column),相比普通column的差別在於field的型別可以更加靈活,比如支援巢狀的文件、陣列。此外,MongoDB中欄位的型別是固定的、區分大小寫、並且文件中的欄位也是有序的。
另外,SQL 還有一些其他的概念,對應關係如下:
SQL概念 | MongoDB概念 |
---|---|
primary key | _id |
foreign key | reference |
view | view |
index | index |
join | $lookup |
transaction | trasaction |
group by | aggregation |
說明
id 主鍵,MongoDB 預設使用一個id 欄位來保證文件的唯一性。
reference 引用,勉強可以對應於 外來鍵(foreign key) 的概念,之所以是勉強是因為 reference 並沒有實現任何外來鍵的約束,而只是由客戶端(driver)自動進行關聯查詢、轉換的一個特殊型別。
view 檢視,MongoDB 3.4 開始支援檢視,和 SQL 的檢視沒有什麼差異,檢視是基於表/集合之上進行動態查詢的一層物件,可以是虛擬的,也可以是物理的(物化檢視)。
index 索引,與SQL 的索引相同。
$lookup,這是一個聚合運算子,可以用於實現類似 SQL-join 連線的功能
transaction 事務,從 MongoDB 4.0 版本開始,提供了對於事務的支援
aggregation 聚合,MongoDB 提供了強大的聚合計算框架,group by 是其中的一類聚合操作。
BSON 資料型別
MongoDB 文件可以使用 Javascript 物件表示,從格式上講,是基於 JSON 的。
一個典型的文件如下:
{ "_id": 1, "name" : { "first" : "John", "last" : "Backus" }, "contribs" : [ "Fortran", "ALGOL", "Backus-Naur Form", "FP" ], "awards" : [ { "award" : "W.W. McDowell Award", "year" : 1967, "by" : "IEEE Computer Society" }, { "award" : "Draper Prize", "year" : 1993, "by" : "National Academy of Engineering" } ]}
曾經,JSON 的出現及流行讓 Web 2.0 的資料傳輸變得非常簡單,所以使用 JSON 語法是非常容易讓開發者接受的。但是 JSON 也有自己的短板,比如無法支援像日期這樣的特定資料型別,因此 MongoDB 實際上使用的是一種擴充套件式的JSON,叫 BSON(Binary JSON)。
BSON 所支援的資料型別包括:
圖-BSON型別
分散式ID
在單機時代,大多數應用可以使用資料庫自增式ID 來作為主鍵。 傳統的 RDBMS 也都支援這種方式,比如 mysql 可以透過宣告 auto_increment來實現自增的主鍵。 但一旦資料實現了分散式儲存,這種方式就不再適用了,原因就在於無法保證多個節點上的主鍵不出現重複。
為了實現分散式資料ID的唯一性保證,應用開發者提出了自己的方案,而大多數方案中都會將ID分段生成,如著名的 snowflake 演算法中就同時使用了時間戳、機器號、程式號以及隨機數來保證唯一性。
MongoDB 採用 ObjectId 來表示主鍵的型別,資料庫中每個文件都擁有一個_id 欄位表示主鍵。_id 的生成規則如下:
圖-ObjecteID
其中包括:
4-byte Unix 時間戳
3-byte 機器 ID
2-byte 程式 ID
3-byte 計數器(初始化隨機)
值得一提的是 id 的生成實質上是由客戶端(Driver)生成的,這樣可以獲得更好的隨機性,同時降低服務端的負載。當然服務端也會檢測寫入的文件是否包含id 欄位,如果沒有就自動生成。
三、操作語法
除了文件模型本身,對於資料的操作命令也是基於JSON/BSON 格式的語法。
比如插入文件的操作:
db.book.insert({ title: "My first blog post", published: new Date(), tags: [ "NoSQL", "MongoDB" ], type: "Work", author : "James", viewCount: 25, commentCount: 2})
執行文件查詢:
db.book.find({author : "James"})
更新文件的命令:
db.book.update( {"_id" : ObjectId("5c61301c15338f68639e6802")}, {"$inc": {"viewCount": 3} })
刪除文件的命令:
db.book.remove({"_id": ObjectId("5c612b2f15338f68639e67d5")})
在傳統的SQL語法中,可以限定返回的欄位,MongoDB可以使用Projection來表示:
db.book.find({"author": "James"}, {"_id": 1, "title": 1, "author": 1})
實現簡單的分頁查詢:
db.book.find({}) .sort({"viewCount" : -1}) .skip(10).limit(5)
這種基於BSON/JSON 的語法格式並不複雜,它的表達能力或許要比SQL更加強大。與 MongoDB 做法類似的還有 ElasticSearch,後者是搜尋資料庫的佼佼者。
關於文件操作與 SQL方式完整的對比,官方的文件描述得比較詳細:
那麼,一個有趣的問題是 MongoDB 能不能用 SQL進行查詢?
當然是可以!
但需要注意這些功能並不是 MongoDB 原生自帶的,而需要藉由第三方工具平臺實現:
客戶端使用SQL,可以使用 mongobooster、studio3t 這樣的工具
服務端的話,可以看看 presto 之類的一些平臺..
四、索引
無疑,索引是一個資料庫的關鍵能力,MongoDB 支援非常豐富的索引型別。利用這些索引,可以實現快速的資料查詢,而索引的型別和特性則是針對不同的應用場景設計的。
索引的技術實現依賴於底層的儲存引擎,在當前的版本中 MongoDB 使用 wiredTiger 作為預設的引擎。在索引的實現上使用了 B+樹的結構,這與其他的傳統資料庫並沒有什麼不同。所以這是個好訊息,大部分基於SQL資料庫的一些索引調優技巧在 MongoDB 上仍然是可行的。
圖-B+樹
使用 ensureIndexes 可以為集合宣告一個普通的索引:
db.book.ensureIndex({author: 1})
author後面的數字 1 代表升序,如果是降序則是 -1
實現複合式(compound)的索引,如下:
db.book.ensureIndex({type: 1, published: 1})
只有對於複合式索引時,索引鍵的順序才變得有意義
如果索引的欄位是陣列型別,該索引就自動成為陣列(multikey)索引:
db.book.ensureIndex({tags: 1})
MongoDB 可以在複合索引上包含陣列的欄位,但最多隻能包含一個
索引特性
在宣告索引時,還可以透過一些引數化選項來為索引賦予一定的特性,包括:
unique=true,表示一個唯一性索引
expireAfterSeconds=3600,表示這是一個TTL索引,並且資料將在1小時後老化
sparse=true,表示稀疏的索引,僅索引非空(non-null)欄位的文件
partialFilterExpression: { rating: { $gt: 5 },條件式索引,即滿足計算條件的文件才進行索引
索引分類
除了普通索引之外,MongoDB 支援的型別還包括:
雜湊(HASH)索引,雜湊是另一種快速檢索的資料結構,MongoDB 的 HASH 型別分片鍵會使用雜湊索引。
地理空間索引,用於支援快速的地理空間查詢,如尋找附近1公里的商家。
文字索引,用於支援快速的全文檢索
模糊索引(Wildcard Index),一種基於匹配規則的靈活式索引,在4.2版本開始引入。
索引評估、調優
使用 explain() 命令可以用於查詢計劃分析,進一步評估索引的效果。如下:
> db.test.explain().find( { a : 5 } )
{
"queryPlanner" : {
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"a" : 5
},
"indexName" : "a_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {"a" : ["[5.0, 5.0]"]}
}
}},
...
}
從結果 winningPlan 中可以看出執行計劃是否高效,比如:
未能命中索引的結果,會顯示COLLSCAN
命中索引的結果,使用IXSCAN
出現了記憶體排序,顯示為 SORT
關於 explain 的結果說明,可以進一步參考文件:
五、叢集
在大資料領域常常提到的4V特徵中,Volume(資料量大)是首當其衝被提及的。由於單機垂直擴充套件能力的侷限,水平擴充套件的方式則顯得更加的靠譜。MongoDB 自帶了這種能力,可以將資料儲存到多個機器上以提供更大的容量和負載能力。此外,同時為了保證資料的高可用,MongoDB 採用副本集的方式來實現資料複製。
一個典型的MongoDB叢集架構會同時採用分片+副本集的方式,如下圖:
圖-MongoDB 分片叢集(Shard Cluster)
架構說明
資料分片(Shards) 分片用於儲存真正的叢集資料,可以是一個單獨的 Mongod例項,也可以是一個副本集。 生產環境下Shard一般是一個 Replica Set,以防止該資料片的單點故障。對於分片集合(sharded collection)來說,每個分片上都儲存了集合的一部分資料(按照分片鍵切分),如果集合沒有分片,那麼該集合的資料都儲存在資料庫的 Primary Shard中。
配置伺服器(Config Servers) 儲存叢集的後設資料(metadata),包含各個Shard的路由規則,配置伺服器由一個副本集(ReplicaSet)組成。
查詢路由(Query Routers) Mongos是 Sharded Cluster 的訪問入口,其本身並不持久化資料 。Mongos啟動後,會從 Config Server 載入後設資料,開始提供服務,並將使用者的請求正確路由到對應的Shard。Sharding 叢集可以部署多個 Mongos 以分擔客戶端請求的壓力。
分片機制
下面的幾個細節,對於理解和應用 MongoDB 的分片機制比較重要,所以有必要提及一下:
1. 資料如何切分
首先,基於分片切分後的資料塊稱為 chunk,一個分片後的集合會包含多個 chunk,每個 chunk 位於哪個分片(Shard) 則記錄在 Config Server(配置伺服器)上。Mongos 在操作分片集合時,會自動根據分片鍵找到對應的 chunk,並向該 chunk 所在的分片發起操作請求。
資料是根據分片策略來進行切分的,而分片策略則由 分片鍵(ShardKey)+分片演算法(ShardStrategy)組成。
MongoDB 支援兩種分片演算法:
範圍分片
如上圖所示,假設集合根據x欄位來分片,x的取值範圍為[minKey, maxKey](x為整型,這裡的minKey、maxKey為整型的最小值和最大值),將整個取值範圍劃分為多個chunk,每個chunk(預設配置為64MB)包含其中一小段的資料:如 Chunk1 包含x的取值在[minKey, -75)的所有文件,而Chunk2包含x取值在[-75, 25)之間的所有文件...
範圍分片能很好的滿足範圍查詢的需求,比如想查詢x的值在[-30, 10]之間的所有文件,這時 Mongos 直接能將請求路由到 Chunk2,就能查詢出所有符合條件的文件。 範圍分片的缺點在於,如果 ShardKey 有明顯遞增(或者遞減)趨勢,則新插入的文件多會分佈到同一個chunk,無法擴充套件寫的能力,比如使用_id作為 ShardKey,而MongoDB自動生成的id高位是時間戳,是持續遞增的。
雜湊分片
Hash分片是根據使用者的 ShardKey 先計算出hash值(64bit整型),再根據hash值按照範圍分片的策略將文件分佈到不同的 chunk。由於 hash值的計算是隨機的,因此 Hash 分片具有很好的離散性,可以將資料隨機分發到不同的 chunk 上。 Hash 分片可以充分的擴充套件寫能力,彌補了範圍分片的不足,但不能高效的服務範圍查詢,所有的範圍查詢要查詢多個 chunk 才能找出滿足條件的文件。
2. 如何保證均衡
如前面的說明中,資料是分佈在不同的 chunk上的,而 chunk 則會分配到不同的分片上,那麼如何保證分片上的 資料(chunk) 是均衡的呢?在真實的場景中,會存在下面兩種情況:
A. 全預分配,chunk 的數量和 shard 都是預先定義好的,比如 10個shard,儲存1000個chunk,那麼每個shard 分別擁有100個chunk。此時叢集已經是均衡的狀態(這裡假定)
B. 非預分配,這種情況則比較複雜,一般當一個 chunk 太大時會產生分裂(split),不斷分裂的結果會導致不均衡;或者動態擴容增加分片時,也會出現不均衡的狀態。 這種不均衡的狀態由叢集均衡器進行檢測,一旦發現了不均衡則執行 chunk資料的搬遷達到均衡。
MongoDB 的資料均衡器執行於 Primary Config Server(配置伺服器的主節點)上,而該節點也同時會控制 Chunk 資料的搬遷流程。
圖-資料自動均衡
對於資料的不均衡是根據兩個分片上的 Chunk 個數差異來判定的,閾值對應表如下:
Number of Chunks | Migration Threshold |
---|---|
Fewer than 20 | 2 |
20-79 | 4 |
80 and greater | 8 |
MongoDB 的資料遷移對叢集效能存在一定影響,這點無法避免,目前的規避手段只能是將均衡視窗對齊到業務閒時段。
3. 應用高可用
應用節點可以透過同時連線多個 Mongos 來實現高可用,如下:
圖- mongos 高可用
當然,連線高可用的功能是由 Driver 實現的。
副本集
副本集又是另一個話題,實質上除了前面架構圖所體現的,副本集可以作為 Shard Cluster 中的一個Shard(片)之外,對於規模較小的業務來說,也可以使用一個單副本集的方式進行部署。MongoDB 的副本集採取了一主多從的結構,即一個 Primary Node + N* Secondary Node的方式,資料從主節點寫入,並複製到多個備節點。
典型的架構如下:
利用副本集,我們可以實現::
資料庫高可用,主節點當機後,由備節點自動選舉成為新的主節點。
讀寫分離,讀請求可以分流到備節點,減輕主節點的單點壓力。
請注意,讀寫分離只能增加叢集"讀"的能力,對於寫負載非常高的情況卻無能為力。對此需求,使用分片叢集並增加分片,或者提升資料庫節點的磁碟IO、CPU能力可以取得一定效果。
選舉
MongoDB 副本集透過 Raft 演算法來完成主節點的選舉,這個環節在初始化的時候會自動完成,如下面的命令:
config = { _id : "my_replica_set", members : [ {_id : 0, host : "rs1.example.net:27017"}, {_id : 1, host : "rs2.example.net:27017"}, {_id : 2, host : "rs3.example.net:27017"}, ]}rs.initiate(config)
initiate 命令用於實現副本集的初始化,在選舉完成後,透過 isMaster()命令就可以看到選舉的結果:
> db.isMaster()
{
"hosts" : [
"192.168.100.1:27030",
"192.168.100.2:27030",
"192.168.100.3:27030"
],
"setName" : "myReplSet",
"setVersion" : 1,
"ismaster" : true,
"secondary" : false,
"primary" : "192.168.100.1:27030",
"me" : "192.168.100.1:27030",
"electionId" : ObjectId("7fffffff0000000000000001"),
"ok" : 1
}
受 Raft演算法的影響,主節點的選舉需要滿足"大多數"原則,可以參考下表:
投票成員數 | 大多數 |
---|---|
1 | 1 |
2 | 2 |
3 | 2 |
4 | 3 |
5 | 3 |
因此,為了避免出現平票的情況,副本集的部署一般採用是基數個節點,比如3個,正所謂三人行必有我師..
心跳
在高可用的實現機制中,心跳(heartbeat)是非常關鍵的,判斷一個節點是否當機就取決於這個節點的心跳是否還是正常的。副本集中的每個節點上都會定時向其他節點傳送心跳,以此來感知其他節點的變化,比如是否失效、或者角色發生了變化。利用心跳,MongoDB 副本集實現了自動故障轉移的功能,如下圖:
預設情況下,節點會每2秒向其他節點發出心跳,這其中包括了主節點。 如果備節點在10秒內沒有收到主節點的響應就會主動發起選舉。此時新一輪選舉開始,新的主節點會產生並接管原來主節點的業務。 整個過程對於上層是透明的,應用並不需要感知,因為 Mongos 會自動發現這些變化。如果應用僅僅使用了單個副本集,那麼就會由 Driver 層來自動完成處理。
複製
主節點和備節點的資料是透過日誌(oplog)複製來實現的,這很類似於 mysql 的 binlog。在每一個副本集的節點中,都會存在一個名為local.oplog.rs的特殊集合。 當 Primary 上的寫操作完成後,會向該集合中寫入一條oplog, 而 Secondary 則持續從 Primary 拉取新的 oplog 並在本地進行回放以達到同步的目的。
下面,看看一條 oplog 的具體形式:
{"ts" : Timestamp(1446011584, 2),"h" : NumberLong("1687359108795812092"),"v" : 2,"op" : "i","ns" : "test.nosql","o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" }}
其中的一些關鍵欄位有:
ts 操作的 optime,該欄位不僅僅包含了操作的時間戳(timestamp),還包含一個自增的計數器值。
h 操作的全域性唯一表示
v oplog 的版本資訊
op 操作型別,比如 i=insert,u=update..
ns 操作集合,形式為 database.collection
o 指具體的操作內容,對於一個 insert 操作,則包含了整個文件的內容
MongoDB 對於 oplog 的設計是比較仔細的,比如:
oplog 必須保證有序,透過 optime 來保證。
oplog 必須包含能夠進行資料回放的完整資訊。
oplog 必須是冪等的,即多次回放同一條日誌產生的結果相同。
oplog 集合是固定大小的,為了避免對空間佔用太大,舊的 oplog 記錄會被滾動式的清理。
有興趣的讀者,可以參考官方文件:
六、事務與一致性
一直以來,"不支援事務" 是 MongoDB 一直被詬病的問題,當然也可以說這是 NoSQL 資料庫的一種權衡(放棄事務,追求高效能、高可擴充套件) 但實質上,MongoDB 很早就有事務的概念,但是這個事務只能是針對單文件的,即單個文件的操作是有原子性保證的。在4.0 版本之後,MongoDB 開始支援多文件的事務:
4.0 版本支援副本集範圍的多文件事務。
4.2 版本支援跨分片的多文件事務(基於兩階段提交)。
在事務的隔離性上,MongoDB 支援快照(snapshot)的隔離級別,可以避免髒讀、不可重複讀和幻讀。儘管有了真正意義上的事務功能,但多文件事務對於效能有一定的影響,應用應該在充分評估後再做選用。
一致性
一致性是一個複雜的話題,而一致性更多從應用角度上提出的,比如:
向系統寫入一條資料,應該能夠馬上讀到寫入的這個資料。
在分散式架構的CAP理論以及許多延續的觀點中提到,由於網路分割槽的存在,要求系統在一致性和可用性之間做出選擇,而不能兩者兼得。
圖 -CAP理論
在 MongoDB 中,這個選擇是可以由開發者來定的。 MongoDB 允許客戶端為其操作設定一定的級別或者偏好,包括:
read preference 讀取偏好,可指定讀主節點、讀備節點,或者是優先讀主、優先讀備、取最近的節點
write concern 寫關注,指定寫入結果達到什麼狀態時才返回,可以為無應答(none)、應答(ack),或者是大多數節點完成了資料複製等等
read concern 讀關注,指定讀取的資料版本處於怎樣的狀態,可以為讀本地、讀大多數節點寫入,或者是線性讀(linearizable)等等。
使用不同的設定將會產生對於C(一致性)、A(可用性)的不同的抉擇,比如:
將讀偏好設定為 primary,此時讀寫都在主節點上。這保證了資料的一致性,但一旦主節點當機會導致失敗(可用性降低)
將讀偏好設定為 secondaryPrefered,此時寫主,優先讀備,可用性提高了,但資料存在延遲(出現不一致)
將讀寫關注都設定為 majority(大多數),一致性提升了,但可用性也同時降低了(節點失效會導致大多數寫失敗)
關於這種權衡的討論會一直存在,而 MongoDB 除了提供多樣化的選擇之外,其主要是透過複製、基於心跳的自動failover等機制來降低系統發生故障時產生的影響,從而提升整體的可用性。
小結
本文主要揭示了 MongoDB 多個方面的細節,同時在使用體驗上也藉助 SQL 的概念做了一些對比。從筆者的角度看,MongoDB 的發展性是很強的,其靈活快速的開發模式、天生自帶分散式等能力彌補了傳統型SQL資料庫的缺陷。當然,目前的 NewSQL 本質上也貌似在以"模仿的方式"彌補這些缺陷。
希望本文的內容對你能有些參考。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69900354/viewspace-2670264/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 瞭解SSL證書,看這一篇就夠了!!
- 瞭解Java中的鎖,看這一篇就夠了!Java
- [翻譯]瞭解NodeJS看這一篇就夠了NodeJS
- Python操作MongoDB看這一篇就夠了PythonMongoDB
- mongoDB看這篇就夠了MongoDB
- 瞭解HandlerThread這一篇就夠了thread
- 【mongodb】增刪改mongodb文件(Document)操作,看這一篇就夠了MongoDB
- Git 看這一篇就夠了Git
- 前端er瞭解GraphQL,看這篇就夠了前端
- MongoDB資料庫效能監控看這一篇就夠了MongoDB資料庫
- Flutter DataTable 看這一篇就夠了Flutter
- 代理模式看這一篇就夠了模式
- Java 集合看這一篇就夠了Java
- 關於SwiftUI,看這一篇就夠了SwiftUI
- 入門Hbase,看這一篇就夠了
- jQuery入門看這一篇就夠了jQuery
- Elasticsearch入門,看這一篇就夠了Elasticsearch
- ActiveMq 之JMS 看這一篇就夠了MQ
- MySQL入門看這一篇就夠了MySql
- flex佈局看這一篇就夠了Flex
- Mybatis入門看這一篇就夠了MyBatis
- 瞭解JS壓縮圖片,這一篇就夠了JS
- mongoDB高階查詢這一篇就夠了MongoDB
- Spring入門看這一篇就夠了Spring
- iOS 動畫詳解(學習動畫看這一篇就夠了)iOS動畫
- 快速瞭解Java多執行緒,有這一篇就夠了Java執行緒
- Nginx 配置常用引數,看這一篇就夠了Nginx
- 分散式事務,只看這一篇就夠了分散式
- 什麼是事件管理?看這一篇就夠了!事件
- Python快速入門,看這一篇就夠了!Python
- 過濾器入門看這一篇就夠了過濾器
- 關於反爬蟲,看這一篇就夠了爬蟲
- MySQL,你只需看這一篇文章就夠了!MySql
- 【詳細圖解】學習佇列,看這一篇就夠了!圖解佇列
- Android混淆——瞭解這些就夠了Android
- Java安全第一篇 | 反射看這一篇就夠了Java反射
- IDEA中的Git操作,看這一篇就夠了!IdeaGit
- Java高階特性泛型看這一篇就夠了Java泛型