侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

MongoDB 索引的最佳实践

数据库  /  管理员 发布于 5年前   285

前言

大部分开发者都知道加索引会快。但实际过程中,我们常碰到一些疑问&困难:

  • 我们查询的字段会各种case都有,是不是各个涉及查询的字段都要加索引?
  • 复合索引和单字段怎么选择,都加还是每一个的单个字段就好了?
  • 加索引有没有副作用?
  • 索引都加了,但还是不够快?怎么办?

本文尝试去解释索引的基本知识&解答上述的疑问。

一、索引究竟是什么东西?

大部分开发者接触索引,大概知道索引类似书的目录,你要找到想要的内容,通过目录找到限定的关键字,进而找到对应的章节的pageno,再找到具体的内容。
在数据结构里面,最简单的索引实现类似hashmap,通过关键字key,映射到具体的位置,找到具体的内容。但除了hash的方式,还有多种的方式实现索引。

(一)索引的多种实现方式以及特色

hash / b-tree / b+-treeredis HSET / MongoDB&PostgreSQL / MySQL

hashmap

bca2bb3a-4c85-4b43-aba5-6c12f9381a6b.jpg

一图见b-tree & b+-tree 差别:

69ff4243-f3fd-4b9e-93ce-35cf9dd7d775.jpg

  • b+-tree 叶子存数据,非叶子存索引,不存数据,叶子间有link
  • b-tree 非叶子可存数据

算法查找复杂度上来说:

  • hash 接近O(1)
  • b-tree O(1)~ O(Log(n))更快的平均查找时间,不稳定的查询时间
  • b+ tree O(Log(n)) 连续数据, 查询的稳定性

至于为何MongoDB 的实现选择b-tree 而非 b+-tree ?
网上多篇文章有阐述,非本文重点。

(二)数据&索引的存储

74b31b12-fe8a-46e5-b30f-a375d8ba29a9.jpgindex尽量存储在内存,data 其次。
注意只保留必要的index,内存尽量用在刀刃上。
如果index memory 都接近占满memory,那么就很容易读到disk,速度就下来了。

(三)知道索引的实现&存储原理后的思考

insert/update/delete 会触发rebalance tree,所以,增删改数据,索引会触发修改,性能会有损耗,索引不是越多越好。既然如此,选哪些字段作为索引呢?当查询用到这些条件,怎么办?
拿一个最简单的hashmap来讲,为什么复杂度不是O(1),而是所谓接近 O(1)。因为有key 冲突/重复,DB 去找的时候,key 冲突的数据一大堆的话,还是得轮着继续找。b-tree 看键(key)的选择也是如此。
因此一个大部分开发经常犯的错就是对没有区分度的key建索引。例如:很多就只有集中类别的 type/status 的 documents count 达几十万以上的collection,通常这种索引没什么帮助。

二、复合索引

(一)复合索引不是越多越好

如果不想多建多余的索引,开发的同事在复合 & 单个字段选择上有时候挺纠结的。根据典型碰到的场景,来做几个实验:
这里创建了个loans collection。简化只有100条数据。这个是借贷的表有 _id, userId, status(借贷状态), amount(金额).

db.loans.count()100

db.loans.find({ "userId" : "59e022d33f239800129c61c7", "status" : "repayed", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "$and" : [   {     "status" : {       "$eq" : "repayed"     }   },   {     "userId" : {       "$eq" : "59e022d33f239800129c61c7"     }   } ]},"queryHash" : "15D5A9A1","planCacheKey" : "15D5A9A1","winningPlan" : { "stage" : "COLLSCAN", "filter" : {   "$and" : [     {       "status" : {         "$eq" : "repayed"       }     },     {       "userId" : {         "$eq" : "59e022d33f239800129c61c7"       }     }   ] }, "direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

注意上面 COLLSCAN 全表扫描了,因为没有索引。接下来我们分别建立几个索引。
step 1 先建立 {userId:1, status:1}

db.loans.createIndex({userId:1, status:1}){"createdCollectionAutomatically" : false,"numIndexesBefore" : 1,"numIndexesAfter" : 2,"ok" : 1}
db.loans.find({ "userId" : "59e022d33f239800129c61c7", "status" : "repayed", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "$and" : [   {     "status" : {       "$eq" : "repayed"     }   },   {     "userId" : {       "$eq" : "59e022d33f239800129c61c7"     }   } ]},"queryHash" : "15D5A9A1","planCacheKey" : "BB87F2BA","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"     ],     "status" : [       "["repayed", "repayed"]"     ]   } }},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

结果:如愿命中 {userId:1, status:1} 作为 winning plan。

step2:再建立个典型的索引 userId

db.loans.createIndex({userId:1}){"createdCollectionAutomatically" : false,"numIndexesBefore" : 2,"numIndexesAfter" : 3,"ok" : 1}
db.loans.find({ "userId" : "59e022d33f239800129c61c7", "status" : "repayed", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "$and" : [   {     "status" : {       "$eq" : "repayed"     }   },   {     "userId" : {       "$eq" : "59e022d33f239800129c61c7"     }   } ]},"queryHash" : "15D5A9A1","planCacheKey" : "1B1A4861","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "[\"59e022d33f239800129c61c7\", \"59e022d33f239800129c61c7\"]"     ],     "status" : [       "[\"repayed\", \"repayed\"]"     ]   } }},"rejectedPlans" : [ {   "stage" : "FETCH",   "filter" : {     "status" : {       "$eq" : "repayed"     }   },   "inputStage" : {     "stage" : "IXSCAN",     "keyPattern" : {       "userId" : 1     },     "indexName" : "userId_1",     "isMultiKey" : false,     "multiKeyPaths" : {       "userId" : [ ]     },     "isUnique" : false,     "isSparse" : false,     "isPartial" : false,     "indexVersion" : 2,     "direction" : "forward",     "indexBounds" : {       "userId" : [         "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"       ]     }   } }]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

留意到 DB 检测到 {userId:1, status:1} 为更优执行的方案.

db.loans.find({ "userId" : "59e022d33f239800129c61c7" }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "userId" : {   "$eq" : "59e022d33f239800129c61c7" }},"queryHash" : "B1777DBA","planCacheKey" : "1F09D68E","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1   },   "indexName" : "userId_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"     ]   } }},"rejectedPlans" : [ {   "stage" : "FETCH",   "inputStage" : {     "stage" : "IXSCAN",     "keyPattern" : {       "userId" : 1,       "status" : 1     },     "indexName" : "userId_1_status_1",     "isMultiKey" : false,     "multiKeyPaths" : {       "userId" : [ ],       "status" : [ ]     },     "isUnique" : false,     "isSparse" : false,     "isPartial" : false,     "indexVersion" : 2,     "direction" : "forward",     "indexBounds" : {       "userId" : [         "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"       ],       "status" : [         "[MinKey, MaxKey]"       ]     }   } }]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

留意到 DB 检测到 {userId:1} 为更优执行的方案,嗯~,如我们所料.

db.loans.find({ "status" : "repayed" }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "status" : {   "$eq" : "repayed" }},"queryHash" : "E6304EB6","planCacheKey" : "7A94191B","winningPlan" : { "stage" : "COLLSCAN", "filter" : {   "status" : {     "$eq" : "repayed"   } }, "direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

有趣的部分:status不命中索引,全表扫描
接下来的步骤,加个sort :

db.loans.find({ "userId" : "59e022d33f239800129c61c7" }).sort({status:1}).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "userId" : {   "$eq" : "59e022d33f239800129c61c7" }},"queryHash" : "F5ABB1AA","planCacheKey" : "764CBAA8","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"     ],     "status" : [       "[MinKey, MaxKey]"     ]   } }},"rejectedPlans" : [ {   "stage" : "SORT",   "sortPattern" : {     "status" : 1   },   "inputStage" : {     "stage" : "SORT_KEY_GENERATOR",     "inputStage" : {       "stage" : "FETCH",       "inputStage" : {         "stage" : "IXSCAN",         "keyPattern" : {           "userId" : 1         },         "indexName" : "userId_1",         "isMultiKey" : false,         "multiKeyPaths" : {           "userId" : [ ]         },         "isUnique" : false,         "isSparse" : false,         "isPartial" : false,         "indexVersion" : 2,         "direction" : "forward",         "indexBounds" : {           "userId" : [ "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"           ]         }       }     }   } }]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

(二)其他尝试

有趣的部分:status 不命中索引

db.loans.find({ "status" : "repayed","userId" : "59e022d33f239800129c61c7", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "$and" : [   {     "status" : {       "$eq" : "repayed"     }   },   {     "userId" : {       "$eq" : "59e022d33f239800129c61c7"     }   } ]},"queryHash" : "15D5A9A1","planCacheKey" : "1B1A4861","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "[\"59e022d33f239800129c61c7\", \"59e022d33f239800129c61c7\"]"     ],     "status" : [       "[\"repayed\", \"repayed\"]"     ]   } }},"rejectedPlans" : [ {   "stage" : "FETCH",   "filter" : {     "status" : {       "$eq" : "repayed"     }   },   "inputStage" : {     "stage" : "IXSCAN",     "keyPattern" : {       "userId" : 1     },     "indexName" : "userId_1",     "isMultiKey" : false,     "multiKeyPaths" : {       "userId" : [ ]     },     "isUnique" : false,     "isSparse" : false,     "isPartial" : false,     "indexVersion" : 2,     "direction" : "forward",     "indexBounds" : {       "userId" : [         "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"       ]     }   } }]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

命中索引,跟query的各个字段顺序不相关,如我们猜测。
有趣部分再来, 我们删掉索引{userId:1}

db.loans.dropIndex({"userId":1}){ "nIndexesWas" : 3, "ok" : 1 }db.loans.find({"userId" : "59e022d33f239800129c61c7", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "userId" : {   "$eq" : "59e022d33f239800129c61c7" }},"queryHash" : "B1777DBA","planCacheKey" : "5776AB9C","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "["59e022d33f239800129c61c7", "59e022d33f239800129c61c7"]"     ],     "status" : [       "[MinKey, MaxKey]"     ]   } }},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

DB 执行分析器觉得索引{userId:1, status:1} 能更优,没有命中复合索引,这个是因为status不是leading field。

db.loans.find({ "status" : "repayed" }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "status" : {   "$eq" : "repayed" }},"queryHash" : "E6304EB6","planCacheKey" : "7A94191B","winningPlan" : { "stage" : "COLLSCAN", "filter" : {   "status" : {     "$eq" : "repayed"   } }, "direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

再换个角度sort 一遍, 与前面query & sort互换,之前是:

db.loans.find({userId:1}).sort({ "status" : "repayed" })

看看有啥不一样?

db.loans.find({ "status" : "repayed" }).sort({userId:1}).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "status" : {   "$eq" : "repayed" }},"queryHash" : "56EA6313","planCacheKey" : "2CFCDA7F","winningPlan" : { "stage" : "FETCH", "filter" : {   "status" : {     "$eq" : "repayed"   } }, "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "userId" : 1,     "status" : 1   },   "indexName" : "userId_1_status_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "userId" : [ ],     "status" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "userId" : [       "[MinKey, MaxKey]"     ],     "status" : [       "[MinKey, MaxKey]"     ]   } }},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

如猜测,命中索引。
再来玩玩,确认下leading filed试验:

db.loans.dropIndex("userId_1_status_1"){ "nIndexesWas" : 2, "ok" : 1 }
db.loans.getIndexes()[{"v" : 2,"key" : { "id" : 1},"name" : "id_","ns" : "cashLoan.loans"}]
db.loans.createIndex({status:1, userId:1}){"createdCollectionAutomatically" : false,"numIndexesBefore" : 1,"numIndexesAfter" : 2,"ok" : 1}
db.loans.getIndexes()[{"v" : 2,"key" : { "id" : 1},"name" : "id_","ns" : "cashLoan.loans"},{"v" : 2,"key" : { "status" : 1, "userId" : 1},"name" : "status_1_userId_1","ns" : "cashLoan.loans"}]
db.loans.find({ "status" : "repayed" }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "status" : {   "$eq" : "repayed" }},"queryHash" : "E6304EB6","planCacheKey" : "7A94191B","winningPlan" : { "stage" : "FETCH", "inputStage" : {   "stage" : "IXSCAN",   "keyPattern" : {     "status" : 1,     "userId" : 1   },   "indexName" : "status_1_userId_1",   "isMultiKey" : false,   "multiKeyPaths" : {     "status" : [ ],     "userId" : [ ]   },   "isUnique" : false,   "isSparse" : false,   "isPartial" : false,   "indexVersion" : 2,   "direction" : "forward",   "indexBounds" : {     "status" : [       "["repayed", "repayed"]"     ],     "userId" : [       "[MinKey, MaxKey]"     ]   } }},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}
db.loans.getIndexes()[{"v" : 2,"key" : { "id" : 1},"name" : "id_","ns" : "cashLoan.loans"},{"v" : 2,"key" : { "status" : 1, "userId" : 1},"name" : "status_1_userId_1","ns" : "cashLoan.loans"}]
db.loans.find({"userId" : "59e022d33f239800129c61c7", }).explain(){"queryPlanner" : {"plannerVersion" : 1,"namespace" : "cashLoan.loans","indexFilterSet" : false,"parsedQuery" : { "userId" : {   "$eq" : "59e022d33f239800129c61c7" }},"queryHash" : "B1777DBA","planCacheKey" : "5776AB9C","winningPlan" : { "stage" : "COLLSCAN", "filter" : {   "userId" : {     "$eq" : "59e022d33f239800129c61c7"   } }, "direction" : "forward"},"rejectedPlans" : [ ]},"serverInfo" : {"host" : "RMBAP","port" : 27017,"version" : "4.1.11","gitVersion" : "1b8a9f5dc5c3314042b55e7415a2a25045b32a94"},"ok" : 1}

看完这个试验,明白了 {userId:1, status:1} vs {status:1,userId:1} 的差别了吗?

PS:这个case 里面其实status 区分度不高,这里只是作为实例展示。

三、总结:

  • 注意使用上、使用频率上、区分高的/常用的在前面;
  • 如果需要减少索引以节省memory/提高修改数据的性能的话,可以保留区分度高,常用的,去除区分度不高,不常用的索引。
  • 学会用explain()验证分析性能:

DB 一般都有执行器优化的分析,MySQL & MongoDB 都是 用explain 来做分析。
语法上MySQL :

explain your_sql

MongoDB:

yoursql.explain()

总结典型:理想的查询是结合explain 的指标,他们通常是多个的混合:

  • IXSCAN : 索引命中;
  • Limit : 带limit;
  • Projection : 相当于非 select * ;
  • Docs Size less is better ;
  • Docs Examined less is better ;
  • nReturned=totalDocsExamined=totalKeysExamined ;
  • SORT in index :sort 也是命中索引,否则,需要拿到数据后,再执行一遍排序;
  • Limit Array elements : 限定数组返回的条数,数组也不应该太多数据,否则schema 设计不合理。

彩蛋

文末,还有最开头1个问题没回答:如果我的索引改加的都加了,还不够快,怎么办?
留个悬念,之后再写一篇。

更多PHP相关技术文章,请访问PHP教程栏目进行学习!

以上就是MongoDB 索引的最佳实践的详细内容,更多请关注其它相关文章!


  • 上一条:
    如何写优雅的SQL原生语句
    下一条:
    MongoDB – 使用模式构建之属性模式
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客