索引操作
前面学习过MySQL,我们知道数据库里给数据构建索引通常能够极大的提高数据查询的效率,缩短查询耗时,如果没有索引,数据库在查询数据时必然会扫描数据表中的每个记录并提取那些符合查询条件的记录。同理,在MongoDB中构建索引也可以提高数据的查询效率和缩短查询耗时,没有索引的情况也是一样,MongoDB也会再查询数据时扫描集合中的每个文档并提取符合查询条件的文档。这种扫描全集合的查询效率是无疑是非常低下的,特别在处理大量的集合数据时,查询时间可能会达到几十秒甚至几分钟,这对用户体验来说是非常致命的。
文档:https://docs.mongodb.com/manual/indexes/
准备数据
// 进入mongo交互终端 mongosh use test // mongoDB中可以使用use切换数据库,针对不存在的数据库会自动创建,可通过db在终端查看当前所在数据库 // 声明了一个函数,生成一个指定随机整数,不足10,前面补0 formatnumber = (start, end)=>{ num = Math.round(Math.random() * (end-start)) + start if(num<10){ return "0"+num; }else{ return num; } } // 声明了一个函数,生成具有随机标题 rand_title = ()=>{ num = Math.round( Math.random() * 10 ); num1 = Math.round( Math.random() * 10 ); return [ "赠送礼品-"+num, "购物狂欢-"+num, "随便买买-"+num, "愉快购物-"+num, "赠送礼物-"+num, "商品购买-"+num, "买多送多-"+num, "买年货-"+num, "买买买买-"+num, "充值会员-"+num ][num1]; } // 创建一个函数,循环生成指定的数据 function rand_data(size=200000){ for(var i=0; i<size; i++){ // 往当前数据库的orders集合(相当于mysql的orders数据表) 添加1条数据 db.orders.insertOne({ "order_number": ( "0000000000000000" + i ).substr( String(i).length ), "date": "20"+formatnumber(0,21)+"-"+formatnumber(1,12)+"-"+formatnumber(1,31), "title": rand_title(i), "user_id": parseInt(i/200)+1, "items" :[{ "goods_id" : parseInt(i/200)+1, "goods_number" : formatnumber(2, 10), "price" : formatnumber(50, 1000) },{ "goods_id" : parseInt(i/200)+2, "goods_number" :formatnumber(2, 10), "price" : formatnumber(50, 1000) }] }) // 判断循环过程中,i每逢1000则打印一次数据 if(i%10000==0){ print("已经添加了"+Math.ceil(i/10000)+"万条数据!"); } } } // 调用上面生成测试数据的函数 rand_data() // 查看上面生成的数据 db.orders.countDocuments() // 每次显示的数据,mongoDB交互终端默认只会显示20条,所以如果要查看更多,则根据提示输入it可以查看下一页数据。View Code
注意事项
-
MongoDB的索引是存储在运行内存(RAM)中的,所以必须确保索引的大小不超过内存的限制。
如果索引的大小超过了运行内存的限制,MongoDB会删除一些索引【这会涉及到mongoDB的驱逐机制,这将导致性能下降】。
-
MongoDB的索引在部分查询条件下是不会生效的。
-
正则表达式及非操作符,如
$nin
,$not
, 等。 -
算术运算符,如 $mod, 等。
-
$where自定义查询函数。
-
sql:select id,name where title=“xiaoming” (分别给id name title 创建索引)
-
mongdb:update({name“:“laoli”}) (给name添加索引)
-
...
-
-
索引会在写入数据(添加、更新和删除)时重排(把原有索引删掉重新排索引),如果项目如果是写多读少,则建议少使用或者不要使用索引。
-
一个集合中索引数量不能超过64个。
-
索引名的长度不能超过128个字符。
-
一个复合索引最多可以有31个字段。
-
mongodb索引统一在
system.indexes
集合中管理。这个集合只能通过createIndex
添加和dropIndexes
删除来操作。
查看索引
// 获取当前集合中已经创建的所有索引信息 db.集合.getIndexes() /* [{ "v" : 2, // 索引版本 "key" : { // 索引的字段及排序方向(1表示升序,-1表示降序) "_id" : 1 // 根据_id字段升序索引 }, "name" : "_id" // 索引的名称 }] */ use admin db.auth("root", "123456") use test // 获取当前集合中已经创建的索引总大小,以字节为单位返回结果 db.users.totalIndexSize()View Code
MongoDB会为插入的文档默认生成_id
字段(如果文档本身没有指定该字段),_id
是文档唯一的主键,为了保证能根据文档id快速查询文档,MongoDB默认会为集合创建_id字段的主键索引。
查询分析(优化用)
与SQL语句类似,MongoDB也提供了一个explain,供开发者进行查询分析,优化查询语句。
explain的使用有3个参数,分别是:queryPlanner、executionStats、allPlansExecution,默认是queryPlanner,开发中常用的是executionStats。
db.orders.find({"title":"愉快购物-6"}).explain("executionStats"); /* { "queryPlanner" : { # 被查询优化器选择出来的查询计划 "plannerVersion" : 1, # 查询计划版本 "namespace" : "test.orders", # 要查询的集合 "indexFilterSet" : false, # 是否了使用索引 "parsedQuery" : { # 查询条件 "title" : { "$eq" : "购买商品-19" } }, queryHash: '244E9C29', // 查询缓存的key值 planCacheKey: '244E9C29', // 查询缓存的key值 "winningPlan" : { # 最佳执行计划 "stage" : "COLLSCAN", # 扫描类型/扫描阶段 "filter" : { # 过滤条件 "title" : { "$eq" : "购买商品-19" } }, "direction" : "forward" # 查询方向,forward为升序,backward表示倒序。 }, "rejectedPlans" : [ ] # 拒绝的执行计划 }, "executionStats" : { # 最佳执行计划的一些统计信息 "executionSuccess" : true, # 是否执行成功 "nReturned" : 1, # 返回的结果数 "executionTimeMillis" : 346, # 执行耗时 "totalKeysExamined" : 0, # 索引扫描次数 "totalDocsExamined" : 1000000, # 文档扫描次数,所谓的优化无非是让totalDocsExamined和nReturned的值接近。 "executionStages" : { # 执行状态 "stage" : "COLLSCAN", # 扫描方式/扫描阶段 "filter" : { "title" : { "$eq" : "购买商品-19" } }, "nReturned" : 1, # 返回的结果数 "executionTimeMillisEstimate" : 5, # 预估耗时 "works" : 1000002, # 工作单元数 "advanced" : 1, # 优先返回的结果数 "needTime" : 1000000, "needYield" : 0, "saveState" : 1000, "restoreState" : 1000, "isEOF" : 1, "direction" : "forward", # 查询方向,forward为升序,backward表示倒序 "docsExamined" : 1000000 # 文档检查数目,与totalDocsExamined一致 } }, "serverInfo" : { # 服务器信息 "host" : "ubuntu", "port" : 27017, "version" : "4.4.2", "gitVersion" : "15e73dc5738d2278b688f8929aee605fe4279b0e" }, "ok" : 1 } */ View Code
stage的扫描类型:(加粗的是命中索引的)
类型名称 | 描述 | 期望 |
---|---|---|
COLLSCAN | 全表扫描 | False |
IXSCAN | 索引扫描 | True |
FETCH | 根据索引去检索指定document | True |
IDHACK | 针对_id进行查询 | True |
COUNTSCAN | count不使用Index进行count时返回 | False |
COUNT_SCAN | count使用了Index进行count时返回 | True |
SUBPLA | 未使用到索引的$or查询时返回 | False |
TEXT | 使用全文索引进行查询时返回 | - |
SORT | 使用sort排序但是无index时返回 | False |
SKIP | 使用skip跳过但是无index时返回 | False |
PROJECTION | 使用limit限定结果但是无index时返回 | False |
创建索引
MongoDB支持多种类型的索引,包括普通索引(也叫单列索引或单字段索引)、复合索引(也叫多字段索引)、多列索引(也叫数组索引)、全文索引、哈希索引、地理位置索引等,每种类型的索引有不同的使用场合。除此之外,还有一种特殊的ttl索引,ttl索引本质上就是普通索引,只是给索引添加一个过期时间而已。另外MongoDB的全文索引很弱智,如果真要用在开发中,还是建议使用elasticsearch或者Sphinx。
// 创建索引 db.集合.createIndex({ // 单个字段,则为普通索引, // sort的值表示排序,值为1表示升序索引,-1表示降序索引 "字段名1": <sort|type>, // type的值可以是text,表示创建全文索引。db.集合.find({$text:{$search:"字符串"}}) "字段名2": <sort|type>, // 多个字段,则为复合索引 "字段名3": [<值1>,<值2>,...], // 多列索引 .... }, { background: <Boolean>, // 建索引过程会阻塞数据库的其它操作,background可指定以后台方式创建索引,默认为false unique: <Boolean>, // 是否建立唯一索引,默认值为false,也叫唯一索引 name: <String>, // 索引的名称,不填写,则MongoDB会通过连接索引的字段名和排序顺序生成一个索引名称 expireAfterSeconds: <integer>, // 设置索引的过期时间,类似redis的expire,也叫TTL索引 sparse: <Boolean>, // 对文档中不存在的字段数据是否不启用索引,默认为False }); // 单字段索引[普通索引] db.集合.createIndex({ "字段名": <sort>, // sort的值表示排序,值为1表示升序索引,-1表示降序索引 }, { .... }) // 普通索引创建: db.orders.createIndex({"order_number":1}) // 查询基本使用: db.orders.find({"order_number":"0000000000030014"}).explain("executionStats"); // 多字段索引,也叫复合索引。[类似mysql里面的联合索引] db.集合.createIndex({ "字段名1": <sort>, // sort的值表示排序,值为1表示升序索引,-1表示降序索引 "字段名2": <sort>, // sort的值表示排序,值为1表示升序索引,-1表示降序索引 "字段名3": <sort>, // sort的值表示排序,值为1表示升序索引,-1表示降序索引 }, { .... }) // 复合索引的使用对单字段条件的查找是没有帮助的,必须多字段[必须包含复合索引的字段]条件使用 // 复合索引创建:db.orders.createIndex({"date":1,"user_id":1}); // 查询基本使用: // db.orders.find({"date":"2002-09-19","user_id":751}).explain("executionStats"); // 全字段匹配,走索引 // db.orders.find({"date":"2014-06-12", "order_number":"0000000000030014"}).explain("executionStats"); // 靠左匹配原则,走索引 // db.orders.find({"user_id":751}).explain("executionStats"); // 不走索引 // 全文索引 db.集合.createIndex({ "字段名1": "text", // type的值只能是text,表示创建全文索引。db.集合.find({$text:{$search:"字符串"}}) }, { .... }) // 全文索引创建: db.orders.createIndex({"title":"text"}); // 查询基本使用: db.orders.find({$text:{$search:"充值会员-1"}}).explain("executionStats") // 多列索引[应用的地方是在数组属性] db.集合.createIndex({ "字段名3": [<值1>,<值2>,...], }, { .... }); // 创建测试数据 db.doc.drop() db.doc.insert({"title":"标题1","tags":["python","django"]}) db.doc.insert({"title":"标题1","tags":["python","django"]}) db.doc.insert({"title":"标题1","tags":["python","django"]}) db.doc.insert({"title":"标题2","tags":["java","mvp"]}) db.doc.insert({"title":"标题3","tags":["java","mvp"]}) db.doc.insert({"title":"标题2","tags":["java","mvp"]}) db.doc.insert({"title":"标题3","tags":["python"]}) db.doc.insert({"title":"标题4","tags":["python"]}) db.doc.insert({"title":"标题2","tags":["python","flask"]}) db.doc.insert({"title":"标题3","tags":["java"]}) // 创建多列索引: db.doc.createIndex({"tags":1}) // 查询数据: db.doc.find({"tags":["python"]}).explain("executionStats") // 唯一索引 db.集合.createIndex({ "字段名1": <sort>, }, { unique: true, // 是否建立唯一索引,默认值为false,也叫唯一索引 }) // 创建唯一索引: db.orders.createIndex({"order_number":1},{unique:true, "name": "order_number_unique_1"}); // 查询数据: db.orders.find({"order_number":"0000000000001019"}).explain("executionStats") // ttl索引 // 使用ttl索引,索引关键字段的值类型必须是Date类型,如果该字段不是date类型或者文档中不存在该字段,则文档不会进行过期处理 // 数据过期的删除工作是在mongoDB中的独立线程内执行的,默认平均60s扫描一次有几率删除,不会立即删除。 // 例如:在文档创建10秒后删除文档 db.orders.createIndex({"date": 1},{expireAfterSeconds: 10}); db.orders.insertOne({ "date": new Date("2022-01-10 17:30:00"), // 在python中需要通过 utctime "user_id": 2, "username": "xiaohong" }) db.orders.insertOne({ "date": new Date("2022-10-12 19:30:00"), // 在python中需要通过 utctime "user_id": 3, "username": "xiaolv" }) // 在文档创建后,由索引字段值指定的时间删除文档 // 创建索引:db.tasks.createIndex({"expire_time":1},{expireAfterSeconds:0}) // 创建测试数据 db.tasks.insertOne( { "expire_time": new Date('2022-10-12 18:14:05'), // 在python中需要通过 utctime "user_id": 2, "username": "xiaoming", }); db.tasks.insertOne( { "expire_time": new Date('2022-10-12 20:16:05'), // 在python中需要通过 utctime "user_id": 2, "username": "xiaoming" }); db.tasks.insertOne( { "expire_time": new Date('2022-10-12 18:20:10'), // 在python中需要通过 utctime "user_id": 3, "username": "xiaoming" }); db.task.find() // 重建索引[一般是在长期项目运行下来,索引创建时间太久了,性能下降的时候使用。] // !!!!不能在高峰期时运行以下操作,会出现阻塞 db.集合.reIndex();View Code
删除索引
MongoDB给文档主键_id
默认创建单字段索引是无法删除的。
// 删除单个索引 db.集合.dropIndex("索引名称") // db.orders.dropIndex("date_1") // 删除所有索引(除了主键_id),慎用 db.集合.dropIndexes()View Code
在python当中,一般常用于开发中操作monoDB的模块无非三个:pymongo(类似于pymysql), mongoEngine, motor
moter是python中基于pymongo实现的异步操作库,类似于aiomysql,aiomysql也是python基于pymysql实现的异步库。
mysql pymysql mysqlDB aiomysql[基于pymysql实现的异步库] redis pyredis redis aioredis[基于pyredis实现的异步库] mongo pymongo mongoengine[基于pymongo实现的ORM,高仿django的ORM] motor[基于pymongo实现的异步库]
PyMongo
文档:https://www.mongodb.com/docs/drivers/python/
文档:https://pymongo.readthedocs.io/en/stable/api/index.html
安装:
pip install pymongo -i https://pypi.douban.com/simple
数据库连接
数据库连接,无密码
import pymongo if __name__ == '__main__': # 无密码连接mongo[如果mongo没有开启用户访问控制机制的情况下,可以使用] mongo = pymongo.MongoClient("mongodb://127.0.0.1:27017") ret = mongo.list_databases()
数据库连接,有密码
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # 有密码连接[最主要的连接方式] username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") print(mongo.list_databases()) # 列出当前账户可操作的所有数据库
数据库管理
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # # 无密码连接mongo[如果mongo没有开启用户访问控制机制的情况下,可以使用] # mongo = pymongo.MongoClient("mongodb://127.0.0.1:27017") # ret = mongo.list_databases() # 有密码连接 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") print(mongo.list_databases()) # 列出当前账户可操作的所有数据库 # 切换操作的数据库 db = mongo["test"] print(db) # 列出当前数据库下的所有集合 print(db.list_collections()) # 获取当前数据库下的指定集合操作对象 users_collection = db["orders"] print(users_collection) # 统计当前集合下所有的文档数量 count = users_collection.count_documents({}) print(count)View Code
集合管理
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # # 无密码连接mongo[如果mongo没有开启用户访问控制机制的情况下,可以使用] # mongo = pymongo.MongoClient("mongodb://127.0.0.1:27017") # ret = mongo.list_databases() # 有密码连接 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") print(mongo.list_databases()) # 列出当前账户可操作的所有数据库 # 切换操作的数据库 db = mongo["test"] print(db) print(db.list_collection_names()) # 列出当前数据库下的所有集合 print(db.list_collections()) # 获取当前数据库下的指定集合操作对象 users_collection = db["orders"] print(users_collection) # 统计当前集合下所有的文档数量 count = users_collection.count_documents({}) print(count) # 删除集合 doc_collection = db["doc"] doc_collection.drop() View Code
文档管理
添加文档
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # 连接数据库 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") # 切换或新建数据库 db = mongo["yingmingapp"] # 切换或新建数据集 users = db["users"] # # 添加一个文档 # document = {"name": "xiaoming", "mobile": "13012345678", "age": 16} # ret = users.insert_one(document) # print(ret.inserted_id) # 返回新增文档的主键ID # 添加多个文档 document_list = [ {"name": "xiaoming", "mobile": "13033345678", "age": 17}, {"name": "xiaohong", "mobile": "13044345678", "age": 18}, {"name": "xiaohei", "mobile": "13612345678", "age": 18}, ] ret = users.insert_many(document_list) print(ret.inserted_ids) # 返回新增文档的主键ID列表View Code
删除文档
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # 连接数据库 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") # 切换或新建数据库 db = mongo["yingmingapp"] # 切换或新建数据集 users = db["users"] """删除文档""" # 删除一篇文档 filter = {"name":"xiaoming"} ret = users.delete_one(filter) print(ret.deleted_count) # 返回删除文档的数量 # 删除多篇文档 filter = {"name": "xiaohong"} ret = users.delete_many(filter) print(ret.deleted_count) # 返回删除文档的数量 # 按指定条件删除多个文档 filter = {"age": {"$gte": 17}} # age >= 17 ret = users.delete_many(filter) print(ret.deleted_count) # 返回删除文档的数量View Code
更新文档
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # 连接数据库 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") # 切换或新建数据库 db = mongo["yingmingapp"] # 切换或新建数据集 users = db["users"] """更新文档""" # # 更新一个文档 # filter = {"name": "xiaoming"} # update = {"$set": {"name": "xiaohong"}, "$inc": {"age": -3}} # ret = users.update_one(filter=filter, update=update) # print(ret.modified_count) # 返回更新文档的数量 # # 更新多个文档 # filter = {"name": "xiaohong"} # update = {"$set": {"sex": True}} # ret = users.update_many(filter=filter, update=update) # print(ret.modified_count) # 返回更新文档的数量 # 把满足条件的一个文档进行替换 filter = {"name": "xiaoming"} replace = {"name": "xiaolan", "age": 16, "sex": False} # 因为是直接替换文档,所以不需要指定更新运算符 ret = users.replace_one(filter, replace) print(ret.modified_count) # 返回替换文档的数量View Code
查询文档
import pymongo from urllib.parse import quote_plus if __name__ == '__main__': # 连接数据库 username = quote_plus("root") password = quote_plus("123456") database = quote_plus("admin") mongo = pymongo.MongoClient(f"mongodb://{username}:{password}@127.0.0.1:27017/{database}") # 切换或新建数据库 db = mongo["test"] # 切换或新建数据集 users = db["users"] """添加文档""" # # 添加一个文档 # document = {"name": "xiaoming", "mobile": "13012345678", "age": 16} # ret = users.insert_one(document) # print(ret.inserted_id) # 返回新增文档的主键ID # # # # 添加多个文档 # document_list = [ # {"name": "xiaoming", "mobile": "13033345678", "age": 17}, # {"name": "xiaohong", "mobile": "13044345678", "age": 18}, # {"name": "xiaohei", "mobile": "13612345678", "age": 18}, # ] # ret = users.insert_many(document_list) # print(ret.inserted_ids) # 返回新增文档的主键ID列表 """删除文档""" # # 删除一篇文档 # filter = {"name":"xiaoming"} # ret = users.delete_one(filter) # print(ret.deleted_count) # 返回删除文档的数量 # # # 删除多篇文档 # filter = {"name": "xiaohong"} # ret = users.delete_many(filter) # print(ret.deleted_count) # 返回删除文档的数量 # # 按指定条件删除多个文档 # filter = {"age": {"$gte": 17}} # age >= 17 # ret = users.delete_many(filter) # print(ret.deleted_count) # 返回删除文档的数量 """更新文档""" # # 更新一个文档 # filter = {"name": "xiaoming"} # update = {"$set": {"name": "xiaohong"}, "$inc": {"age": -3}} # ret = users.update_one(filter=filter, update=update) # print(ret.modified_count) # 返回更新文档的数量 # # 更新多个文档 # filter = {"name": "xiaohong"} # update = {"$set": {"sex": True}} # ret = users.update_many(filter=filter, update=update) # print(ret.modified_count) # 返回更新文档的数量 # # 把满足条件的一个文档进行替换 # filter = {"name": "xiaoming"} # replace = {"name": "xiaolan", "age": 16, "sex": False} # 因为是直接替换文档,所以不需要指定更新运算符 # ret = users.replace_one(filter, replace) # print(ret.modified_count) # 返回替换文档的数量 """查询文档""" # # 获取整个集合所有的文档数据 # orders = db["orders"] # ducument_list = orders.find() # for document in ducument_list: # print(document) # # 获取集合中第一个文档 # orders = db["orders"] # document = orders.find_one() # print(document) # print(document.get("order_number")) # 获取文档中的任意数据 # print(document.get("items")) # 获取文档中的任意数据 # print(document["items"][0]) # 获取文档中的任意数据 # print(document["items"][0]["price"]) # 获取文档中的任意数据 # # 投影显示字段 # document = users.find_one({}, {"_id": 0}) # print(document) # # document = users.find_one({}, {"_id": 0, "name": 1, "age": 1}) # print(document) # 按条件查询多条数据 # filter = {"age": 18} # document_list = users.find(filter) # for document in document_list: # print(document) # # 比较运算符 # filter = {"age": {"$gt":17}} # document_list = users.find(filter) # for document in document_list: # print(document) # # 结果排序,单字段排序 # filter = {"age": {"$lt": 32}} # # pymongo.ASCENDING 就是 -1 ,表示升序, 从小到大 # # pymongo.DESCENDING 就是 1 ,表示降序, 从大到小 # document_list = users.find(filter).sort("age", pymongo.DESCENDING) # print(list(document_list)) # # 结果排序,多字段排序 # filter = {"age": {"$lte": 34}} # order = [("age", pymongo.DESCENDING), ("_id", pymongo.ASCENDING)] # document_list = users.find(filter).sort(order) # print(list(document_list)) # # 限制查询结果数量 # document_list = users.find().limit(3) # print(list(document_list)) # # 偏移、跳过 # # skip(int) # document_list = users.find().limit(3).skip(3) # 从第3篇文档开始获取3篇文档 # print(list(document_list)) # # 自定义条件函数 # document_list = users.find({"$where": "this.age==18"}) # print(list(document_list)) ret = db.command("buildinfo") print(ret)View Code
标签:users,MongoDB,db,索引,文档,print,document From: https://www.cnblogs.com/erhuoyuan/p/17416871.html