网站seo优化报告,贵安新区网站建设,百度提交入口网站网址,昆山市建设监察大队官方网站文章目录 一、数据库查询效率问题引出索引需求二、索引的基本原理及作用#xff08;一#xff09;索引的创建及数据组织#xff08;二#xff09;不同类型的索引#xff08;三#xff09;索引的额外属性 三、索引的优化与查询计划分析#xff08;一#xff09;通过prof… 文章目录 一、数据库查询效率问题引出索引需求二、索引的基本原理及作用一索引的创建及数据组织二不同类型的索引三索引的额外属性 三、索引的优化与查询计划分析一通过profiling监测慢请求二查询计划分析优化索引使用 四、查询聚合优化一案例背景问题描述问题分析1. 定位慢查询2. 分析慢查询语句第一步$match操作第二步$project操作第三步$group操作 查看DB/Server/Collection的状态1. DB状态2. 查看orders这个collection的状态 性能优化1. 性能优化 - 索引2. 性能优化 - 聚合大量数据 小结 更多相关内容可查看 一、数据库查询效率问题引出索引需求
当在使用MongoDB等数据库进行集合查询时如果遇到查询效率低下的情况就可能需要考虑使用索引了。以MongoDB为例在向集合插入多个文档后每个文档经过底层存储引擎持久化会有一个位置信息如mmapv1引擎里是『文件id 文件内offset』wiredtiger存储引擎里是其生成的一个key通过这个位置信息能从存储引擎里读出该文档。
mongo-9552:PRIMARYgt; db.person.find()
{ _id : ObjectId(571b5da31b0d530a03b3ce82), name : jack, age : 19 }
{ _id : ObjectId(571b5dae1b0d530a03b3ce83), name : rose, age : 20 }
{ _id : ObjectId(571b5db81b0d530a03b3ce84), name : jack, age : 18 }
{ _id : ObjectId(571b5dc21b0d530a03b3ce85), name : tony, age : 21 }
{ _id : ObjectId(571b5dc21b0d530a03b3ce86), name : adam, age : 18 }假设要执行一个查询操作比如db.person.find( {age: 18} )如果没有索引就需要遍历所有的文档即进行“全表扫描”根据位置信息读出文档后再对比age字段是否为18。当集合文档数量较少时全表扫描的开销可能不大但当文档数量达到百万、千万甚至上亿时全表扫描的开销会非常大一个查询耗费数十秒甚至几分钟都有可能。
二、索引的基本原理及作用
一索引的创建及数据组织
比如上面的例子里person集合里包含插入了5个文档假设其存储后位置信息如下
位置信息文档pos1{“name” : “jack”, “age” : 19 }pos2{“name” : “rose”, “age” : 20 }pos3{“name” : “jack”, “age” : 18 }pos4{“name” : “tony”, “age” : 21}pos5{“name” : “adam”, “age” : 18}
如果想加速 db.person.find( {age: 18} 就可以考虑对person表的age字段建立索引。
db.person.createIndex( {age: 1} ) // 按age字段创建升序索引建立索引后MongoDB会额外存储一份按age字段升序排序的索引数据索引结构类似如下索引通常采用类似btree的结构持久化存储以保证从索引里快速O(logN)的时间复杂度找出某个age值对应的位置信息然后根据位置信息就能读取出对应的文档。
age位置信息18pos318pos519pos120pos221pos4
简单来说索引就是将文档按照某个或某些字段顺序组织起来以便能根据该字段高效地进行查询。它至少能优化以下场景的效率
查询场景比如查询年龄为18的所有人有了索引就无需全表扫描可直接通过索引快速定位到符合条件的文档。更新/删除场景在将年龄为18的所有人的信息进行更新或删除时因为更新或删除操作需要先根据条件查询出所有符合条件的文档所以本质上也是在优化查询环节。排序场景将所有人的信息按年龄排序时如果没有索引需要全表扫描文档然后再对扫描的结果进行排序而有了索引可利用索引的有序性更高效地完成排序。
MongoDB默认会为插入的文档生成_id字段如果应用本身没有指定该字段并且为了保证能根据文档id快速查询文档MongoDB默认会为集合创建_id字段的索引。
mongo-9552:PRIMARYgt; db.person.getIndexes() // 查询集合的索引信息
[{ns : test.person, // 集合名v : 1, // 索引版本key : { // 索引的字段及排序方向_id : 1 // 根据_id字段升序索引},name : _id_ // 索引的名称}
]二不同类型的索引
MongoDB支持多种类型的索引每种类型适用于不同的使用场合 单字段索引Single Field Index 通过db.person.createIndex( {age: 1} )语句可针对age创建单字段索引能加速对age字段的各种查询请求是最常见的索引形式MongoDB默认创建的_id索引也属于这种类型。{age: 1}代表升序索引也可通过{age: -1}来指定降序索引对于单字段索引升序/降序效果是一样的。db.person.createIndex( {age: 1, name: 1} ) 复合索引 (Compound Index) 它是单字段索引的升级版本针对多个字段联合创建索引先按第一个字段排序第一个字段相同的文档按第二个字段排序依次类推。例如通过db.person.createIndex( {age: 1, name: 1} )可针对age、name这2个字段创建一个复合索引。复合索引能满足的查询场景比单字段索引更丰富不光能满足多个字段组合起来的查询如db.person.find( {age 18 name: jack} )也能满足匹配复合索引前缀的查询如{age: 1}是{age: 1, name: 1}的前缀所以db.person.find( {age 18} )的查询也能通过该索引来加速但像db.person.find( {name: jack} )这种只涉及部分字段且不符合前缀规则的查询则无法使用该复合索引。在创建复合索引时字段的顺序除了受查询需求影响还需考虑字段的值分布情况。比如age字段取值有限相同age的文档较多而name字段取值丰富相同name的文档较少此时先按name字段查找再在相同name的文档里查找age字段会更为高效。db.person.createIndex( {name: 1, age: 1} ) 多key索引 Multikey Index 当索引的字段为数组时创建出的索引称为多key索引。例如在person表加入一个habbit字段数组用于描述兴趣爱好通过db.person.createIndex( {habbit: 1} )可自动创建多key索引用于查询有相同兴趣爱好的人。 {name : jack, age : 19, habbit: [football, runnning]}db.person.createIndex( {habbit: 1} ) // 自动创建多key索引db.person.find( {habbit: football} )其他类型索引 哈希索引Hashed Index按照某个字段的hash值来建立索引目前主要用于MongoDB Sharded Cluster的Hash分片hash索引只能满足字段完全匹配的查询不能满足范围查询等。地理位置索引Geospatial Index能很好地解决O2O的应用场景比如“查找附近的美食”、“查找某个区域内的车站”等。文本索引Text Index能解决快速文本查找的需求比如对于一个博客文章集合可针对博客的内容建立文本索引以便根据博客内容快速查找。
三索引的额外属性
MongoDB除了支持多种不同类型的索引还能对索引定制一些特殊的属性
唯一索引 (unique index)保证索引对应的字段不会出现相同的值比如_id索引就是唯一索引。TTL索引可以针对某个时间字段指定文档的过期时间经过指定时间后过期 或 在某个时间点过期。部分索引 (partial index)只针对符合某个特定条件的文档建立索引在3.2版本才支持该特性。稀疏索引(sparse index)只针对存在索引字段的文档建立索引可看做是部分索引的一种特殊情况。
三、索引的优化与查询计划分析
一通过profiling监测慢请求
MongoDB支持对DB的请求进行profiling目前支持3种级别的profiling
0级不开启profiling。1级将处理时间超过某个阈值默认100ms的请求都记录到DB下的system.profile集合类似于mysql、redis的slowlog生产环境通常建议使用此级别并根据自身需求配置合理的阈值用于监测慢请求的情况以便及时进行索引优化。2级将所有的请求都记录到DB下的system.profile集合生产环境需慎用。
二查询计划分析优化索引使用
当索引已经建立了但查询还是很慢时就需要深入分析索引的使用情况可通过查看详细的查询计划来决定如何优化。通过执行计划可以看出以下问题
根据某个/些字段查询但没有建立索引。根据某个/些字段查询但建立了多个索引执行查询时没有使用预期的索引。
例如建立索引前db.person.find( {age 18} )必须执行COLLSCAN全表扫描
mongo-9552:PRIMARYgt; db.person.find({age: 18}).explain()
{queryPlanner : {plannerVersion : 1,namespace : test.person,indexFilterSet : false,parsedQuery : {age : {$eq : 18}},winningPlan : {stage : COLLSCAN,filter : {age : {$eq : 18}},direction : forward},rejectedPlans : [ ]},serverInfo : {host : localhost,port : 9552,version : 3.2.3,gitVersion : b326ba837cf6f49d65c2f85e1b70f6f31ece7937},ok : 1
}建立索引后通过查询计划可以看出先进行[IXSCAN](从索引中查找)然后FETCH读取出满足条件的文档。
mongo-9552:PRIMARYgt; db.person.find({age: 18}).explain()
{queryPlanner : {plannerVersion : 1,namespace : test.person,indexFilterSet : false,parsedQuery : {age : {$eq : 18}},winningPlan : {stage : FETCH,inputStage : {stage : IXSCAN,keyPattern : {age : 1},indexName : age_1,isMultiKey : false,isUnique : false,isSparse : false,isPartial : false,indexVersion : 1,direction : forward,indexBounds : {age : [[18.0, 18.0]]}}},rejectedPlans : [ ]},serverInfo : {host : localhost,port : 9552,version : 3.2.3,gitVersion : b326ba837cf6f49d65c2f85e1b70f6f31ece7937},ok : 1
}需要注意的是索引并不是越多越好集合的索引太多会影响写入、更新的性能因为每次写入都需要更新所有索引的数据。所以system.profile里的慢请求可能是索引建立得不够导致也可能是索引过多导致。
四、查询聚合优化
一案例背景
我们有一个电商订单分析系统使用MongoDB存储订单数据。当执行一个分析接口获取特定店铺在某一周内的订单商品分类统计信息时发现查询速度非常慢严重影响用户体验。
问题描述
执行订单分析接口查询特定店铺假设店铺ID为“20001”在某一周2024 - 05 - 01T00:00:00.000Z到2024 - 05 - 07T23:59:59.999Z内的订单商品分类统计需要花费约12秒这明显不符合性能要求。
问题分析
1. 定位慢查询
首先查看当前mongo profile的级别通过db.getProfilingLevel()发现其为0即默认没有记录。设置profile级别为记录慢查询模式设置阈值为1000ms即db.setProfilingLevel(1, 1000)。再次执行订单分析查询接口查看Profile记录。
2. 分析慢查询语句
通过查看Profile记录发现执行的查询是一个聚合管道pipeline
第一步$match操作
{$match: {storeId: 20001,$and: [{orderTime: {$gte: ISODate(2024-05-01T00:00:00.000Z),$lte: ISODate(2024-05-07T23:59:59.999Z)}}]}
}用于匹配店铺ID为“20001”且订单时间在指定一周内的订单记录。
第二步$project操作
{$project: {productCategory: 1,orderDate: {$concat: [{$substr: [{$year: [$orderTime]},0,4]},-,{$substr: [{$month: [$orderTime]},0,2]},-,{$substr: [{$dayOfMonth: [$orderTime]},0,2]}]}}
}除了提取productCategory字段外还对orderTime字段进行处理拼接为“yyyy - MM - dd”格式并将其命名为orderDate。
第三步$group操作
{$group: {_id: {orderDate: $orderDate,productCategory: $productCategory},count: {$sum: 1}}
}对orderDate和productCategory进行分组统计不同日期和商品分类对应的订单数量。
从Profile中可以看到相关指标
millis花费了12010毫秒返回查询结果。ts命令执行时间。info命令内容。query代表查询。nsecommerce.orders代表查询的库与集合。nreturned返回记录数及用时。reslen返回的结果集大小字节数。nscanned扫描记录数量。
发现nscanned数很大接近记录总数可能没有使用索引查询。
查看DB/Server/Collection的状态
1. DB状态
查看数据库整体状态包括服务器版本、运行时间、连接数、各种操作计数器如插入、查询、更新、删除等操作的次数、存储引擎信息等。示例部分信息如下
{host: ECOMMONGODB,version: 6.0.5,process: mongod,pid: NumberLong(2005),uptime: 12345678.0,uptimeMillis: NumberLong(12345678901),uptimeEstimate: NumberLong(12345678),localTime: ISODate(2024-05-08T10:20:30.123Z),asserts: {regular: 0,warning: 0,msg: 0,user: 12345,rollovers: 0},connections: {current: 120,available: 800,totalCreated: 13000},// 其他更多信息...ok: 1.0
}2. 查看orders这个collection的状态
{ns: ecommerce.orders,size: 987654321,count: 3500000,avgObjSize: 282,storageSize: 234567890,capped: false,wiredTiger: {// wiredTiger存储引擎相关详细信息...},nindexes: 1,totalIndexSize: 30123456,indexSizes: {_id_: 30123456},ok: 1.0
}性能优化
1. 性能优化 - 索引
目前只有_id索引接下来对orders集合创建storeId、orderTime和productCategory字段的索引
db.orders.ensureIndex({storeId: 1, orderTime: 1, productCategory: 1});
db.orders.ensureIndex({orderTime: 1});
db.orders.ensureIndex({productCategory: 1});
db.orders.ensureIndex({storeId: 1});创建索引后查询特定店铺一周内的订单商品分类统计信息时间缩短到了500ms效果显著。但当查询一个月的数据时仍然需要15秒。
通过增加索引小结添加索引解决了针对索引字段查询的效率问题但对于大量数据的聚合操作仅靠索引不能完全解决性能问题。例如在没有索引的情况下从500万条数据中找出特定店铺的订单可能需要全表扫描耗时很长而有了索引命中索引查询IXSCAN速度提升明显。不过对于聚合操作随着数据量增大性能问题依然存在。同时判断效率优化情况应该看执行计划而不仅仅是执行时间因为执行时间可能受到多种因素影响。
2. 性能优化 - 聚合大量数据
对于这种查询聚合大量数据的问题考虑到这是一个类似OLAP的操作对其性能期望不能过高因为大量数据的I/O操作远超OLTP操作。但仍有一定的优化空间
在订单插入或更新时对每个店铺每天的每个商品分类的订单数量进行实时计数并存储在一个专门的缓存集合中。例如以{storeId: 20001, orderDate: 2024-05-01, productCategory: Electronics, count: 10}的形式存储。每隔一段时间如每天凌晨对缓存集合进行一次完整的统计和更新确保数据的准确性。这样在查询订单商品分类统计信息时可以直接从缓存集合中获取数据大大减少了查询和聚合的时间。
小结
慢查询定位通过Profile分析慢查询。查询优化通过添加相应索引提升查询速度。聚合大数据方案对于类似OLAP的聚合操作要合理降低性能期望。从源头入手在数据插入或更新时做好部分统计工作缓存结果以便在查询时直接使用从而提升整体性能。同时要结合执行计划来评估优化效果。