mongodb核心原始碼實現及效能優化系列:Mongodb write寫(增、刪、改)模組原始碼實現
Mongodb write 寫 ( 增、刪、改 ) 模組原始碼實現
前面的《transport_layer 網路傳輸層模組原始碼實現》和《 command 命令處理模組原始碼實現》詳細的分析了 mongodb 核心網路資料收發過程以及命令解析處理的整個過程,本文將繼續分析該系列的第三個子模組 - 《 write 寫 ( 增、刪、改 ) 模組原始碼實現》。
1. write 寫模組與 command 命令處理模組銜接回顧
上面兩圖是command 命令處理模組的大體流程,最終經過 command 模組處理後,會執行對應的命令 run 介面,本文要分析的 write 模組也將從本入口入手。增、刪、改三個最基本的寫操作對應的命令入口如下表:
操作型別 |
命令 Run() 入口 |
增 |
CmdInsert::runImpl() |
刪 |
CmdDelete::runImpl() |
改 |
CmdUpdate::runImpl() |
mongodb 核心 write 模組主要由如下目錄程式碼實現:
下面章節將分析增刪改操作的詳細核心實現流程,注意包括請求序列化解析儲存、insert 寫入流程、 update 更新計劃執行器、 delete 刪除計劃執行器等。
2. 增、刪、改序列化解析及結構化統一儲存
本章節詳細分析增、刪、改三個操作的序列化解析及結構化統一儲存核心實現過程。
2.1 增刪改寫入操作語法及其主要含義說明
l insert 插入語法及說明
insert 主要完成資料的寫入操作,其命令語法如下:
1. {
2. insert: <collection>,
3. documents: [ <document>, <document>, <document>, ... ],
4. ordered: <boolean>,
5. writeConcern: { <write concern> },
6. bypassDocumentValidation: <boolean>
7. }
insert 操作主要由五個欄位型別組成,具體欄位功能說明如下:
欄位名 |
功能說明 |
insert |
集合名 |
documents |
具體的文件內容 |
ordered |
一次性插入多條文件資料,前面的資料寫入失敗,是否繼續後面的資料寫入 |
writeConcern |
writeConcern 寫策略配置,寫多少個節點才算成功 |
bypassDocumentValidation |
是否進行 validator 相關 schema 文件驗證 |
l update 更新語法及說明
update 操作實現資料更新操作,其命令語法如下:
1. {
2. update: <collection>,
3. updates: [
4. { q: <query>, u: <update>, upsert: <boolean>, multi: <boolean>,
5. collation: <document>, arrayFilters: <array> },
6. { q: <query>, u: <update>, upsert: <boolean>, multi: <boolean>,
7. collation: <document>, arrayFilters: <array> },
8. { q: <query>, u: <update>, upsert: <boolean>, multi: <boolean>,
9. collation: <document>, arrayFilters: <array> },
10. ...
11. ],
12. ordered: <boolean>,
13. writeConcern: { <write concern> },
14. bypassDocumentValidation: <boolean>
15. }
上述語法各欄位功能說明如表:
欄位名 |
功能說明 |
update |
對那個表做 update 操作 |
updates .q |
查詢條件 |
updates .u |
更新操作方法 |
updates . upsert |
如果需要更新的資料不存在,是否直接插入新資料 |
updates . multi |
query 滿足條件資料有多條,是隻更新一條還是多條一起更新 |
updates . collation |
根據不同語言定義不同排序規則 |
updates . arrayFilters |
陣列中成員內容跟新 |
ordered |
一次更新多條文件資料,前面的資料更新失敗,是否繼續後面的資料更新操作 |
writeConcern |
更新多少個節點成功才返回 OK |
bypassDocumentValidation |
是否進行 validator 相關 schema 文件驗證 |
l delete 更新語法及說明
delete 刪除操作對應語法如下:
1. {
2. delete: <collection>,
3. deletes: [
4. { q : <query>, limit : <integer>, collation: <document> },
5. { q : <query>, limit : <integer>, collation: <document> },
6. { q : <query>, limit : <integer>, collation: <document> },
7. ...
8. ],
9. ordered: <boolean>,
10. writeConcern: { <write concern> }
11. }
如上,delete 語法各個欄位功能說明如下:
欄位名 |
功能說明 |
delete |
對那個表做 delete 操作 |
deletes .q |
需要刪除那一部分資料,也就是刪除資料的條件 |
deletes . limit |
刪除所有滿足條件的資料還是隻刪除一條,取值 0 或 1 |
deletes . collation |
根據不同語言定義不同排序規則 |
ordered |
刪除一批資料,如果前面某資料刪除失敗,是否還需要刪除後面滿足條件的資料 |
writeConcern |
刪除多少個節點成功才返回 OK |
2.2 增、刪、改序列化解析
2.2.1 增、刪、改核心資料結構
從上面的insert 、 delete 、 update 語法可以看出,這三個操作有一部分欄位名是一樣的,核心在程式碼實現的時候也重複利用了這一特定,把這部分成員抽象為公共類,不同的欄位則在各自操作類中封裝。
最終,三個操作的欄位資訊通過公用類WriteCommandBase 和各自私有類 Insert 、 Update 、 Delete 保持及解析封裝。如下圖所示:
公共基類由WriteCommandBase 類實現,如下:
1. class WriteCommandBase {
2. public:
3. //基類介面
4. ......
5. //mongodb欄位驗證規則(schema validation)
6. bool _bypassDocumentValidation{false};
7. //一次對多條資料進行插入或者刪除或者更新的時候,前面的資料操作失敗,是否繼續後面的操作
8. bool _ordered{true};
9. //事務相關,等4.2版本回頭分析
10. boost::optional<std::vector<std::int32_t>> _stmtIds;
11. }
Insert 類包含 WriteCommandBase 類成員,同時包括 Insert 操作對應的私有成員資訊,如下:
1. class Insert {
2. public:
3. ......
4. //也就是db.collection
5. NamespaceString _nss;
6. //公共結構資訊
7. WriteCommandBase _writeCommandBase;
8. //真正的文件在這裡documents
9. std::vector<mongo::BSONObj> _documents;
10. //庫資訊
11. std::string _dbName;
12. //是否有documents
13. }
delete 刪除操作對應 Delete 類核心成員資訊如下:
1. class Delete {
2. public:
3. ......
4. //DB.COLLECTION資訊
5. NamespaceString _nss;
6. WriteCommandBase _writeCommandBase;
7. //具體的delete內容在這裡
8. std::vector<DeleteOpEntry> _deletes;
9. }
update 更新操作對應的 Update 類核心成員資訊如下 :
1. class Update {
2. public:
3. ......
4. //db.collection資訊,也就是庫.表資訊
5. NamespaceString _nss;
6. WriteCommandBase _writeCommandBase;
7. //需要更新的具體內容在該成員中
8. std::vector<UpdateOpEntry> _updates;
9. }
上面的類結構中, _documents 、 _deletes 、 _updates 三個成員分別對應增、刪、改操作的集體操作資訊,都是陣列型別,可以一次進行多條資料操作。
2.2.2 增、刪、改解析過程
增刪改三個操作對應三個不同的類,由這三個類來完成各自操作的協議解析及封裝,整體程式碼實現大同小異,本文只分析insert 解析及封裝過程,主要程式碼實現如下 :
1. Insert Insert::parse(const IDLParserErrorContext& ctxt, const BSONObj& bsonObject) {
2. ......
3. //呼叫Insert::parseProtected
4. object.parseProtected(ctxt, bsonObject);
5. return object;
6. }
7.
8. void Insert::parseProtected(...)
9. {
10. //解析出insert類的對應成員資訊
11. for (const auto& element :request.body) {
12. const auto fieldName = element.fieldNameStringData();
13.
14. //解析bypassDocumentValidation資訊
15. if (fieldName == kBypassDocumentValidationFieldName) {
16. ......
17. }
18. //解析ordered資訊
19. else if (fieldName == kOrderedFieldName) {
20. ......
21. }
22. //解析stmtIds資訊
23. else if (fieldName == kStmtIdsFieldName) {
24. ......
25. }
26. //解析需要插入的文件資訊
27. else if (fieldName == kDocumentsFieldName) {
28. //解析的文件保持到_documents陣列
29. _documents = std::move(values);
30. }
31. //解析db名
32. else if (fieldName == kDbNameFieldName) {
33. ......
34. }
35. ......
36. }
37. //從request中解析出_writeCommandBase基礎成員內容
38. _writeCommandBase = WriteCommandBase::parse(ctxt, request.body);
39.
40. ......
41. //根據db+collection構造出db.collection字串
42. _nss = ctxt.parseNSCollectionRequired(_dbName, commandElement);
43. }
和insert 操作類似, update 和 delete 操作的解析過程與 insert 流程一樣比較簡單,因此不在分析。
最終,所有解析出的資料儲存到各自類中,總結如下圖所示:
此外,增刪改操作的序列化封裝由write_ops_gen.cpp 中的 Insert::serialize() 、 Update::serialize() 、 Delete::serialize() 完成,主要根據各自類完成 Bson 統一封裝,整個實現過程比較簡單,這裡不在詳細分析。
增刪改介面解析及序列化相關幾個核心介面功能說明如下:
類 |
函式介面 |
功能說明 |
write_ops::Insert |
InsertOp::parse(...) |
insert 操作解析 |
Insert::toBSON(...) |
insert Bson 序列化 | |
write_ops::Update |
UpdateOp::parse(...) |
update 操作解析 |
Update::toBSON(...) |
update Bson 序列化 | |
write_ops::Delete |
DeleteOp::parse(...) |
delete 操作解析 |
DeleteOp::toBSON(...) |
delete Bson 序列化 |
注意: 在insert 、 update 、 delete 中還有如下一個細節,為何不見 writeConcern 相關成員儲存?原因是 writeConcern 解析放到了外層 runCommandImpl 中通過 setWriteConcern() 保持到該請求對應得 opCtx 操作上下文中。
3. Insert 資料寫操作核心實現
insert 處理和 command 命令處理模組通過 CmdInsert::runImpl() 銜接,該介面程式碼實現如下:
1. //插入文件會走這裡面 CmdInsert::runImpl
2. void runImpl (... ) final {
3. //從request中解析出write_ops::Insert類成員資訊
4. const auto batch = InsertOp::parse(request);
5. const auto reply = performInserts(opCtx, batch);
6. ......
7. }
InsertOp::parse () 在前面章節已經分析,主要完成資料的統一解析儲存。 insert 請求解析儲存到 write_ops::Insert 類後,開始呼叫 performInserts (...) 處理。在該介面中完成如下流程:分批資料組裝、批量資料寫入、事務封裝、寫入儲存引擎等。
3.1 資料分批組裝
由於inset 一次可以插入多條資料,為了最大化滿足效能要求,當寫入資料很多的時候, mongodb 核心通過把這些資料按照指定規則拆分到多個 batch 中,這樣每個 batch 代表一批資料,然後進行統一處理。分批資料組裝拆分過程核心程式碼實現如下 :
1. //資料分批寫入核心程式碼實現
2. WriteResult performInserts(OperationContext* opCtx, const write_ops::Insert& wholeOp) {
3. .......
4. //寫入資料成功後的會掉處理
5. //主要完成表級tps及時延統計
6. ON_BLOCK_EXIT([&] {
7. //performInserts執行完成後呼叫,記錄執行結束時間
8. curOp.done();
9. //表級tps及時延統計
10. Top::get(opCtx->getServiceContext())
11. .record(...);
12.
13. });
14.
15. ......
16. size_t bytesInBatch = 0;
17. //batch陣列
18. std::vector<InsertStatement> batch;
19. //預設64,可以通過db.adminCommand( { setParameter: 1, internalInsertMaxBatchSize:xx } )配置
20. const size_t maxBatchSize = internalInsertMaxBatchSize.load();
21. //當寫入的資料小於64時,也就是一個batch即可一起處理
22. //batch最大限制為寫入資料大於64或者batch中總位元組數超過256K
23. batch.reserve(std::min(wholeOp.getDocuments().size(), maxBatchSize));
24. for (auto&& doc : wholeOp.getDocuments()) {
25. ......
26. //doc檢查,例如是否巢狀過多,是否一個doc帶有多個_id等
27. auto fixedDoc = fixDocumentForInsert(opCtx->getServiceContext(), doc);
28. //如果這個文件檢測有異常,則跳過這個文件,進行下一個文件操作
29. if (!fixedDoc.isOK()) {
30. //啥也不做,直接忽略該doc
31. } else {
32. //事務相關,先忽略,以後會回頭專門分析事務
33. const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++);
34. ......
35. //把文件插入到batch陣列
36. BSONObj toInsert = fixedDoc.getValue().isEmpty() ? doc : std::move(fixedDoc.getValue());
37. batch.emplace_back(stmtId, toInsert);
38. bytesInBatch += batch.back().doc.objsize();
39. //這裡continue,就是為了把批量插入的文件組成到一個batch陣列中,到達一定量一次性插入
40. //batch裡面一次最多插入64個文件或者總位元組數256K,則後續的資料拆分到下一個batch
41. if (!isLastDoc && batch.size() < maxBatchSize && bytesInBatch < insertVectorMaxBytes)
42. continue; // Add more to batch before inserting.
43. }
44.
45. //把本batch中的資料交由該介面統一處理
46. bool canContinue = insertBatchAndHandleErrors(opCtx, wholeOp, batch, &lastOpFixer, &out);
47. //清空batch,開始下一輪處理
48. batch.clear();
49. bytesInBatch = 0;
50. ......
51. }
上面的程式碼可以總結為以下圖形:
說明,上面假設64 條資料總大小不超過 256KB 的 batch 圖,如果 64 條 doc 文件資料總大小超過 256kb ,這時候閥值則以總資料 256K 為限制。單個 batch 最大上限限制條件如下:
l 最多64 個 doc 文件資料。
l 單個batch 總資料長度不超過 256Kb 。
3.2 batch 資料事務寫入流程及其異常補償機制
一批資料通過分批拆分存入多個batch 後,呼叫 insertBatchAndHandleErrors() 介面來完成單個 batch 的資料寫入。整個 batch 資料寫入可以在一個 transaction 事務完成,也可以一條資料一個事務來完成寫入,具體核心程式碼實現如下 :
1. bool insertBatchAndHandleErrors(...) {
2. ......
3. try {
4. //如果對應collection不存在則建立
5. acquireCollection(); //執行上面定義的函式
6. //如果collection不是固定capped集合,並且batch中資料大於一條
7. //則試著在一個事務中一次性寫入所有的資料
8. if (!collection->getCollection()->isCapped() && batch.size() > 1) {
9. ......
10. //為什麼這裡沒有檢查返回值?預設全部成功? 實際上通過try catch獲取到異常後,再後續改為一條一條插入
11. insertDocuments(opCtx, collection->getCollection(), batch.begin(), batch.end());
12. //insert統計計數及返回值賦值
13. globalOpCounters.gotInserts(batch.size());
14. ......
15. std::fill_n(std::back_inserter(out->results), batch.size(), std::move(result));
16. curOp.debug().ninserted += batch.size();
17. //一個事務寫入多個doc成功,直接返回
18. return true;
19. }
20. } catch (const DBException&) { //批量寫入失敗,則後面一條一條的寫
21. collection.reset();
22. //注意這裡沒有return,在後續一條一個事務寫入
23. }
24.
25. //這裡迴圈解析batch,實現一條資料一個在一個事務中處理
26. for (auto it = batch.begin(); it != batch.end(); ++it) {
27. globalOpCounters.gotInsert(); //insert操作計數
28. try {
29. //log() << "yang test ............getNamespace().ns():" << wholeOp.getNamespace().ns();
30. //writeConflictRetry裡面會執行{}中的函式體
31. writeConflictRetry(opCtx, "insert", wholeOp.getNamespace().ns(), [&] {
32. try {
33. ......
34. //把該條文件插入
35. insertDocuments(opCtx, collection->getCollection(), it, it + 1);
36. //統計計數處理
37. SingleWriteResult result;
38. result.setN(1);
39. out->results.emplace_back(std::move(result));
40. curOp.debug().ninserted++;
41. } catch (...) {
42. ......
43. }
44. });
45. } catch (const DBException& ex) {//寫入異常
46. //注意這裡,如果失敗是否還可以繼續後續資料的寫入
47. bool canContinue =
48. handleError(opCtx, ex, wholeOp.getNamespace(), wholeOp.getWriteCommandBase(), out);
49. if (!canContinue)
50. return false; //注意這裡直接退出迴圈,也就是本批次資料後續資料沒有寫入了
51. }
52. }
53.
54. return true;
55. }
一批batch 資料 ( 假設 64 條 ) 寫入過程,如果不是 capped 固定集合,則這 64 條資料首先放入一個 transaction 事務中完成寫入。如果寫入異常,則繼續一個事務一條資料寫入。資料放入事務執行流程如下 :
1. void insertDocuments(OperationContext* opCtx,
2. Collection* collection,
3. std::vector<InsertStatement>::iterator begin,
4. std::vector<InsertStatement>::iterator end)
5. //事務開始
6. WriteUnitOfWork wuow(opCtx);
7. ......
8. //把陣列begin到end之間的所有doc文件資料放入該事務中
9. uassertStatusOK(collection->insertDocuments(
10. opCtx, begin, end, &CurOp::get(opCtx)->debug(), /*enforceQuota*/ true));
11. //事務結束
12. wuow.commit(); //WriteUnitOfWork::commit
13. }
到這裡後,insert 操作在 write 模組中的流程就結束了,後續的 doc 寫入流程儲存引擎將交由 storage 模組實現。
上面的核心程式碼分析可以總結為如下總結:
當這個batch 中的資料放入同一個事務執行失敗後,則改為一條一個事務迴圈處理,如下圖所示:
3.3 中間資料寫入異常如何處理
假設一個batch 資料 64 條資料,如果第 23 條資料寫入失敗了,後續的第 24-64 條資料是否需要繼續寫入,這就是本章節需要分析的問題。 mongodb 核心實現的時候通過 handleError() 介面判斷是否需要繼續寫入,該介面程式碼如下:
1. // 前面資料寫入失敗,是否可以繼續後續資料寫入
2. bool handleError(...) {
3. ......
4.
5. //判斷是什麼原因引起的異常,從而返回不同的值
6. //如果是isInterruption錯誤,直接返回true,意思是不需要後續資料寫入
7. if (ErrorCodes::isInterruption(ex.code())) {
8. //如果是interrupt異常,則整批資料寫失敗,也就是不進行後續資料寫入
9. throw; // These have always failed the whole batch.
10. }
11.
12. ......
13. //如果ordered為false則忽略這條寫入失敗的資料,繼續後續資料寫入
14. return !wholeOp.getOrdered();
15. }
從上面的程式碼可以看出,只要出現以下異常情況,就不可繼續後續資料insert 寫入操作了,如下:
l Interruption 錯誤: 包括Interrupted 、 InterruptedAtShutdown 、 ExceededTimeLimit 、 InterruptedDueToReplStateChange 四種異常,其他異常情況可以繼續寫入。
l ordered 引數配置為 false: 如果該配置為false 則遇到異常不繼續處理後續 doc 寫入。
寫入異常後是否繼續寫總結如下圖所示:
3.4 後續
通過前面的分析可以得出,mongodb 核心把多條 doc 文件按照指定限制把文件封裝到不同 batch 中,然後一個 batch 一個 batch 分批處理。最終,這些 batch 對應資料將會通過 mongodb 核心的 storage 儲存模組來完成 insert 事務處理,最終在 CollectionImpl::insertDocuments() 實現。
Insert 寫入流程核心介面呼叫關係圖如下:
說明:資料如何組裝存入wiredtiger 儲存引擎將在後續《 storage 儲存模組原始碼實現》中詳細分析。
4. delete 刪除操作核心實現
delete 資料刪除通過命令處理模組中的 CmdDelete::runImpl(...) ->performDeletes 介面完成和 write 寫模組 delete 操作對接,下面我們分析該介面核心程式碼實現,如下:
1. WriteResult performDeletes(...)
2. {
3. ......
4.
5. //singleOp型別為DeleteOpEntry write_ops::Delete::getDeletes
6. for (auto&& singleOp : wholeOp.getDeletes()) {
7. //事務相關,先跳過,以後相關章節專門分析
8. const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++);
9. ......
10.
11. //該函式介面執行完後執行該finishCurOp
12. //finishCurOp實現表級QPS及時延統計 本op操作的慢日誌記錄等
13. ON_BLOCK_EXIT([&] { finishCurOp(opCtx, &curOp); });
14. try {
15. lastOpFixer.startingOp();
16. out.results.emplace_back(
17. //該delete op操作真正執行在這裡,singleOp型別為DeleteOpEntry
18. performSingleDeleteOp(opCtx, wholeOp.getNamespace(), stmtId, singleOp));
19. lastOpFixer.finishedOpSuccessfully();
20. } catch (const DBException& ex) {
21. ......
22. }
23.
24. return out;
25. }
從上面程式碼分析可以看出,如果wholeOp 攜帶有多個 DeleteOpEntry( 也就是 singleOp ) 操作,則迴圈對 singleOp 進行處理,這個處理過程由performSingleDeleteOp(...) 介面實現,具體如下 :
performSingleDeleteOp(...) 介面核心程式碼實現如下:
1. static SingleWriteResult performSingleDeleteOp(...) {
2. ......
3.
4. //根據ns構造DeleteReques
5. //根據請求相關資訊初始化賦值DeleteRequest
6. DeleteRequest request(ns);
7. request.setQuery(op.getQ());
8. request.setCollation(write_ops::collationOf(op));
9. request.setMulti(op.getMulti());
10. request.setYieldPolicy(PlanExecutor::YIELD_AUTO); // ParsedDelete overrides this for $isolated.
11. request.setStmtId(stmtId);
12.
13. //根據DeleteRequest構造ParsedDelete
14. ParsedDelete parsedDelete(opCtx, &request);
15. //從request解析出對應成員存入parsedDelete
16. uassertStatusOK(parsedDelete.parseRequest());
17. //檢查該請求是否已經被kill掉了
18. opCtx->checkForInterrupt();
19.
20. ......
21. //寫必須走主節點判斷及版本判斷
22. assertCanWrite_inlock(opCtx, ns);
23.
24. //從查詢引擎中獲取delete執行器
25. auto exec = uassertStatusOK(
26. getExecutorDelete(opCtx, &curOp.debug(), collection.getCollection(), &parsedDelete));
27.
28. {
29. stdx::lock_guard<Client> lk(*opCtx->getClient());
30. CurOp::get(opCtx)->setPlanSummary_inlock(Explain::getPlanSummary(exec.get()));
31. }
32.
33. //執行該執行器
34. uassertStatusOK(exec->executePlan());
35.
36. //下面流程是記錄各種統計資訊
37. long long n = DeleteStage::getNumDeleted(*exec);
38. curOp.debug().ndeleted = n;
39.
40. PlanSummaryStats summary;
41. //獲取執行器執行過程中的各種統計資訊
42. Explain::getSummaryStats(*exec, &summary);
43. if (collection.getCollection()) {
44. collection.getCollection()->infoCache()->notifyOfQuery(opCtx, summary.indexesUsed);
45. }
46. curOp.debug().setPlanSummaryMetrics(summary);
47. //統計資訊序列化
48. if (curOp.shouldDBProfile()) {
49. BSONObjBuilder execStatsBob;
50. Explain::getWinningPlanStats(exec.get(), &execStatsBob);
51. curOp.debug().execStats = execStatsBob.obj();
52. }
53.
54. ......
55. return result;
56. }
該介面最核心的部分為獲取delete 執行器並執行,執行器由 query 查詢引擎模組實現,因此 getExecutorDelete(...) 獲取 delete 執行器及其執行過程具體實現流程將在後續《 query 查詢引擎模組實現原理》章節詳細分析,這裡暫時跳過這一邏輯。 write 模組中 delete 操作主要介面呼叫流程如下:
5. update 更新操作核心實現
update 資料更新操作過程和 delete 操作過程類似,這裡不在累述,其核心介面呼叫流程如下圖所示:
6. 下期預告
下期將分析《storage 儲存模組原始碼實現》, storage 模組分析完成後將分析 mongodb 最複雜的《 query 查詢引擎原始碼實現》,敬請關注。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984922/viewspace-2761596/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Mongodb write寫(增、刪、改)模組原始碼實現MongoDB原始碼
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-mongodb網路傳輸層模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現一MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現三MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現四MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-網路傳輸層模組原始碼實現二MongoDB原始碼運維
- mongodb核心原始碼實現、效能調優、最佳運維實踐系列-command命令處理模組原始碼實現二MongoDB原始碼運維
- mongodb核心原始碼實現及效能最佳化:transport_layer網路傳輸層模組原始碼實現二MongoDB原始碼
- mongodb原始碼實現、調優、最佳實踐系列-數百萬行mongodb核心原始碼閱讀經驗分享MongoDB原始碼
- mongodb核心原始碼實現及效能最佳化系列:Mongodb特定場景效能數十倍提升最佳化實踐MongoDB原始碼
- mongodb核心原始碼實現、效能調優系列-為何要對開源mongodb資料庫核心做二次開發MongoDB原始碼資料庫
- 萬字長文 | MongoDB絡傳輸處理原始碼實現及效能調優MongoDB原始碼
- mongodb網路傳輸處理原始碼實現及效能調優-體驗核心效能極致設計MongoDB原始碼
- mongodb核心transport_layer網路傳輸層模組原始碼實現三MongoDB原始碼
- mongodb核心transport_layer 網路傳輸層模組原始碼實現四MongoDB原始碼
- mongodb核心原始碼實現及效能最佳化:常用高併發執行緒模型設計及mongodb執行緒模型最佳化實踐MongoDB原始碼執行緒模型
- webpack4+express+mongodb+vue 實現增刪改查WebExpressMongoDBVue
- 使用express+mongoose對mongodb實現增刪改查操作ExpressMongoDB
- python 連線mongodb實現增刪改查例項PythonMongoDB
- express+mongodb+vue實現增刪改查-全棧之路ExpressMongoDBVue全棧
- 使用node和express+mongodb實現資料增刪改功能ExpressMongoDB
- express+mongodb+vue實現增刪改查-全棧之路2.0ExpressMongoDBVue全棧
- MongoDB——簡單增、刪、改、查實踐MongoDB
- Mongodb原始碼分析--刪除記錄MongoDB原始碼
- 婚戀app原始碼開發,如何實現介面效能優化?APP原始碼優化
- 70行實現Promise核心原始碼Promise原始碼
- Laravel核心程式碼學習 — Model增刪改查底層實現Laravel
- Laravel核心程式碼學習 -- Model增刪改查底層實現Laravel
- 封裝模組實現商品增刪改查封裝
- MongoDB增刪改查操作MongoDB
- MongoDB的增刪改查MongoDB
- mongodb 基本增刪改查MongoDB
- 實現語音社交原始碼介面效能優化,從索引入手原始碼優化索引
- Vue原始碼探究-核心類的實現Vue原始碼
- HashMap實現原理及原始碼分析HashMap原始碼
- zookeeper原始碼(10)node增刪改查及監聽原始碼
- PHP操作MongoDB(增刪改查)PHPMongoDB