MongoDB indexoptimization

MasterMongoDB indexclass型, creationmethod, queryoptimization and indexmaintenancetechniques

indexIntroduction

index is MongoDBinimprovingqueryperformance 关键techniques. 它class似于书籍 Table of Contents, 可以helpingdatalibrarysystem fast 速定位 to 需要query data, 而无需扫描整个collection.

index 作用

  • improvingquery速度: throughindex, datalibrary可以 fast 速定位 to 匹配 documentation, reducing扫描 data量
  • 加速sortoperation: such as果querypackage含sortoperation, usingindex可以避免 in memoryinsort
  • optimizationaggregateoperation: 某些aggregateoperation也可以利用indeximprovingperformance
  • 强制唯一性: 唯一index可以确保字段值 唯一性

index Pros and Cons

优点 缺点
improvingqueryperformance 占用额 out store空间
加速sort and aggregateoperation 降 low 写入performance (每次写入都需要updateindex)
强制data唯一性 增加indexmaintenance complex 性
support complex query模式 过 many index会降 low 整体performance

提示: index is 一把双刃剑, 合理using可以显著improvingperformance, 但过度using会带来负面影响. in designindex时, 需要根据practical query模式 and data特点for权衡.

indexclass型

1. 单字段index

单字段index is 最basic indexclass型, 基于单个字段creationindex.

// creation单字段index
db.users.createIndex({ "name": 1 })  // 1表示升序, -1表示降序

// 查看index
db.users.getIndexes()

2. 复合index

复合index基于 many 个字段creationindex, index 顺序很 important .

// creation复合index
db.users.createIndex({ "age": 1, "name": -1 })

// 复合index using规则
// 1.  before 缀principles: 可以利用index  before 缀部分
// 例such as, { "age": 1, "name": -1 } 可以用于query { "age": { "$gt": 30 } }
// 2. 顺序principles: index 顺序决定了query efficiency
// 例such as, { "age": 1, "name": -1 }  for 于 { "name": "张三" } query无法 has 效利用index

3. many 键index

many 键index用于array字段, MongoDB会 for arrayin 每个元素creationindex项.

// creation many 键index
db.users.createIndex({ "hobbies": 1 })

// testdata
db.users.insertOne({
    "name": "张三",
    "hobbies": ["读书", "旅游", "programming"]
})

// query会using many 键index
db.users.find({ "hobbies": "programming" })

4. 地理空间index

地理空间index用于地理位置data query.

// creation2dsphereindex (用于GeoJSON格式) 
db.places.createIndex({ "location": "2dsphere" })

// creation2dindex (用于平面坐标) 
db.places.createIndex({ "location": "2d" })

5. 文本index

文本index用于全文搜索.

// creation文本index
db.articles.createIndex({ "content": "text", "title": "text" })

// using文本indexfor搜索
db.articles.find({ "$text": { "$search": "MongoDB index" } })

6. 哈希index

哈希indexusing哈希function for 字段值for哈希processing, 适用于etc.值query.

// creation哈希index
db.users.createIndex({ "username": "hashed" })

// 哈希index 特点
// 1. 只supportetc.值query, 不support范围query
// 2. 哈希index queryperformance通常比单字段index差
// 3. 主要用于shard键 哈希shard策略

7. 唯一index

唯一index确保字段值 唯一性.

// creation唯一index
db.users.createIndex({ "email": 1 }, { "unique": true })

// creation复合唯一index
db.users.createIndex({ "firstName": 1, "lastName": 1 }, { "unique": true })

// 尝试插入重复值会失败
db.users.insertOne({ "email": "zhangsan@example.com" })
db.users.insertOne({ "email": "zhangsan@example.com" })  // 会失败

indexcreationmethod

createIndex()method

usingcreateIndex()methodcreationindex, 语法such as under :

// basic语法
db.collection.createIndex(
    { field1: direction, field2: direction, ... },  // index字段 and 方向
    { option1: value1, option2: value2, ... }        // index选项
)

index选项

选项 describes example
unique creation唯一index { "unique": true }
name 指定index名称 { "name": "age_name_index" }
background after 台creationindex (不阻塞otheroperation) { "background": true }
sparse creation稀疏index (只package含 has 该字段 documentation) { "sparse": true }
expireAfterSeconds 设置过期时间 (用于TTLindex) { "expireAfterSeconds": 3600 }
weights 设置文本index 权重 { "weights": { "title": 3, "content": 1 } }

index命名

MongoDB会自动 for index生成名称, 但也可以手动指定index名称.

// 自动生成 index名称
db.users.createIndex({ "age": 1, "name": -1 })  // index名称: age_1_name_-1

// 手动指定index名称
db.users.createIndex({ "age": 1, "name": -1 }, { "name": "age_name_idx" })

查看 and deleteindex

// 查看collection 所 has index
db.users.getIndexes()

// delete指定index
db.users.dropIndex("age_1_name_-1")  // throughindex名称delete
db.users.dropIndex({ "age": 1, "name": -1 })  // throughindex定义delete

// delete所 has index (除了_idindex) 
db.users.dropIndexes()

queryoptimization

1. index覆盖query

index覆盖query is 指query 所 has 字段都package含 in indexin, MongoDB可以直接 from indexin返回结果, 而不需要访问documentation.

// creation复合index
db.users.createIndex({ "age": 1, "name": 1, "email": 1 })

// index覆盖query (只返回indexin 字段) 
db.users.find(
    { "age": { "$gt": 30 } },
    { "name": 1, "email": 1, "_id": 0 }
)

// 非index覆盖query (需要访问documentation) 
db.users.find(
    { "age": { "$gt": 30 } },
    { "name": 1, "address": 1, "_id": 0 }
)

2. indexsort

such as果querypackage含sortoperation, usingindex可以避免 in memoryinsort.

// creationindex
db.users.createIndex({ "age": 1, "name": 1 })

// 利用indexsort ( high 效) 
db.users.find({ "age": { "$gt": 30 } }).sort({ "name": 1 })

// 无法利用indexsort (需要 in memoryinsort) 
db.users.find({ "age": { "$gt": 30 } }).sort({ "email": 1 })

3. index before 缀

复合indexsupport before 缀匹配, 即可以利用index before 几个字段forquery.

// creation复合index
db.users.createIndex({ "age": 1, "name": 1, "email": 1 })

// 可以利用index query
// 1.  before 缀query
db.users.find({ "age": 30 })

// 2.  before 缀+ after 续字段query
db.users.find({ "age": 30, "name": "张三" })

// 3.  before 缀范围query
db.users.find({ "age": { "$gt": 30 } })

// 无法 has 效利用index query
// 1. 跳过 before 缀字段
db.users.find({ "name": "张三" })

// 2. 非 before 缀字段范围query
db.users.find({ "name": { "$gt": "张三" } })

4. query计划analysis

usingexplain()methodanalysisquery计划, Understandindex usingcircumstances.

// analysisquery计划
db.users.find({ "age": { "$gt": 30 }, "name": "张三" }).explain()

// 查看详细 执行statisticsinformation
db.users.find({ "age": { "$gt": 30 }, "name": "张三" }).explain("executionStats")

// example输出解读
// "winningPlan": 表示MongoDB选择 最佳执行计划
// "stage": 执行阶段, such as "IXSCAN" (index扫描)  or  "COLLSCAN" (collection扫描) 
// "keyPattern": using index模式
// "indexName": using index名称
// "executionStats.nReturned": 返回 documentation数量
// "executionStats.totalKeysExamined": check index键数量
// "executionStats.totalDocsExamined": check documentation数量

5. queryoptimizationtechniques

  • usingindex字段forquery: 优先using已creationindex 字段serving asquery条件
  • 避免全collection扫描: for 于 big 型collection, 全collection扫描会导致performanceissues
  • 限制返回字段: 只返回需要 字段, reducingnetwork传输 and memoryusing
  • using$hint指定index: in 某些circumstances under , MongoDB可能选择error index, 可以using$hint指定
  • 避免using$whereoperation符: $whereoperation符会导致全collection扫描
  • 避免using正则表达式开头 query: such as /.*test/ 会导致全collection扫描

indexmaintenance

1. indexmanagement

  • 定期checkindexusingcircumstances: delete未using index
  • monitorindex big small : 过 big index会占用 big 量store空间
  • 合理designindex: 根据practicalquery模式designindex
  • 避免过 many index: 每个collection index数量不宜过 many

2. indexmonitor

// 查看indexusingcircumstances (需要开启 profiling) 
db.setProfilingLevel(1)  // 1表示记录 slow query

// 查看 slow querylog
db.system.profile.find().sort({ "ts": -1 }).limit(10)

// 查看collection statisticsinformation
db.users.stats()

// 查看index  big  small 
db.users.stats().indexSizes

3. indexoptimization

// 重建index (optimizationindexstructure) 
db.users.reIndex()

//  after 台重建index (不阻塞otheroperation) 
db.users.reIndex({ "background": true })

// analysiscollection (updatestatisticsinformation, helpingqueryoptimization器) 
db.users.aggregate([{ "$collStats": { "latencyStats": { "histograms": true } } }])

4. TTLindex

TTL (Time-To-Live) index可以自动delete过期 documentation, 适用于log, sessionetc.临时data.

// creationTTLindex
db.logs.createIndex({ "createdAt": 1 }, { "expireAfterSeconds": 3600 })  // 1 small 时 after 过期

// 插入testdata
db.logs.insertOne({
    "message": "testlog",
    "createdAt": new Date()
})

// Notes: 
// 1. TTLindex只能基于单个日期 or 日期时间字段
// 2. MongoDB会每60秒run一次 after 台taskdelete过期documentation
// 3. 过期时间 is 基于index字段 值加 on expireAfterSeconds计算 
// 4. TTLindex不能 is 复合index 一部分

5. indexbest practices

  • 根据query模式designindex: analysisapplication query模式, for 常用querycreationindex
  • 优先using复合index: for 于 many 字段query, 复合index比 many 个单字段index更 high 效
  • 注意复合index 顺序: 将选择性 high 字段放 in before 面
  • using唯一index保证dataintegrity: for 于需要唯一值 字段using唯一index
  • 定期maintenanceindex: delete未using index, 重建碎片化 index
  • monitorindexperformance: usingexplain() and profilinganalysisindexusingcircumstances

实践case

case1: userqueryoptimization

fake设我们 has 一个usercollection, package含以 under 字段: _id, name, age, email, addressetc.. applicationin常用 queryincluding:

  1. 按年龄范围queryuser
  2. 按姓名 and 年龄组合queryuser
  3. 按邮箱queryuser (loginverification)
  4. 按地址queryuser
// 1. analysisquery模式
// 常用query: 
// db.users.find({ "age": { "$gt": 30, "$lt": 40 } })
// db.users.find({ "name": "张三", "age": 30 })
// db.users.find({ "email": "zhangsan@example.com" })
// db.users.find({ "address.city": "北京" })

// 2. designindex
// creationindex
db.users.createIndex({ "age": 1 })  // 用于年龄范围query
db.users.createIndex({ "name": 1, "age": 1 })  // 用于姓名 and 年龄组合query
db.users.createIndex({ "email": 1 }, { "unique": true })  // 用于邮箱query, 保证唯一性
db.users.createIndex({ "address.city": 1 })  // 用于地址query

// 3. optimizationquery
// usingindex覆盖query
db.users.find(
    { "age": { "$gt": 30, "$lt": 40 } },
    { "name": 1, "email": 1, "_id": 0 }
)

// analysisquery计划
db.users.find({ "name": "张三", "age": 30 }).explain()

// 4. monitor and maintenance
// 查看indexusingcircumstances
db.users.getIndexes()

// 查看index big  small 
db.users.stats().indexSizes

case2: 电商产品queryoptimization

fake设我们 has 一个产品collection, package含以 under 字段: _id, name, category, price, stock, createdAtetc.. applicationin常用 queryincluding:

  1. 按class别 and 价格范围query产品
  2. 按名称模糊query产品
  3. 按creation时间sortquery产品
  4. querylibrary存 big 于0 产品
// 1. analysisquery模式
// 常用query: 
// db.products.find({ "category": "手机", "price": { "$gt": 2000, "$lt": 5000 } })
// db.products.find({ "name": /iPhone/ })
// db.products.find().sort({ "createdAt": -1 })
// db.products.find({ "stock": { "$gt": 0 } })

// 2. designindex
// creation复合index
db.products.createIndex({ "category": 1, "price": 1 })  // 用于class别 and 价格范围query

// creation文本index
db.products.createIndex({ "name": "text" })  // 用于名称搜索

// creation单字段index
db.products.createIndex({ "createdAt": -1 })  // 用于按creation时间sort

db.products.createIndex({ "stock": 1 })  // 用于library存query

// 3. optimizationquery
// 利用复合indexquery
db.products.find({ "category": "手机", "price": { "$gt": 2000, "$lt": 5000 } })

// using文本index搜索
db.products.find({ "$text": { "$search": "iPhone" } })

// 利用indexsort
db.products.find({ "stock": { "$gt": 0 } }).sort({ "createdAt": -1 })

// 4. analysisquery计划
db.products.find({ "category": "手机", "price": { "$gt": 2000, "$lt": 5000 } }).explain()

互动练习

练习1: indexdesign

fake设我们 has 一个博客文章collection, package含以 under 字段: _id, title, author, category, tags (array) , content, createdAt, viewsetc.. applicationin常用 queryincluding: 1. 按作者 and creation时间query文章 2. 按class别query文章并按creation时间sort 3. 按tagquery文章 4. 按标题模糊query文章 5. 按浏览量sortquery文章 请design合适 index来optimization这些query.

referencedesign:

// 1. 按作者 and creation时间query文章
db.articles.createIndex({ "author": 1, "createdAt": -1 })

// 2. 按class别query文章并按creation时间sort
db.articles.createIndex({ "category": 1, "createdAt": -1 })

// 3. 按tagquery文章
db.articles.createIndex({ "tags": 1 })  //  many 键index

// 4. 按标题模糊query文章
db.articles.createIndex({ "title": "text" })  // 文本index

// 5. 按浏览量sortquery文章
db.articles.createIndex({ "views": -1 })

练习2: queryoptimization

给定以 under index and query, analysis哪些query可以利用index, 哪些不能, 并说明原因.
// index: 
db.users.createIndex({ "age": 1, "name": 1, "email": 1 })

// query: 
// 1. db.users.find({ "age": 30 })
// 2. db.users.find({ "name": "张三" })
// 3. db.users.find({ "age": 30, "name": "张三" })
// 4. db.users.find({ "age": 30, "email": "zhangsan@example.com" })
// 5. db.users.find({ "name": "张三", "age": 30, "email": "zhangsan@example.com" })
// 6. db.users.find({ "age": { "$gt": 30 } })
// 7. db.users.find({ "age": { "$gt": 30 }, "name": "张三" })
// 8. db.users.find({ "email": "zhangsan@example.com" })
                    

analysis结果:

// 1. db.users.find({ "age": 30 })
// 可以利用index: usingindex  before 缀字段

// 2. db.users.find({ "name": "张三" })
// 不能 has 效利用index: 跳过了index  before 缀字段 "age"

// 3. db.users.find({ "age": 30, "name": "张三" })
// 可以利用index: usingindex  before 两个字段

// 4. db.users.find({ "age": 30, "email": "zhangsan@example.com" })
// 可以部分利用index: usingindex 第一个字段 "age", 但 "email" 不 is index 第二个字段

// 5. db.users.find({ "name": "张三", "age": 30, "email": "zhangsan@example.com" })
// 可以利用index: 虽然字段顺序不同, 但MongoDB会optimization

// 6. db.users.find({ "age": { "$gt": 30 } })
// 可以利用index: usingindex  before 缀字段for范围query

// 7. db.users.find({ "age": { "$gt": 30 }, "name": "张三" })
// 可以利用index: usingindex  before 两个字段, 其in第一个字段 is 范围query

// 8. db.users.find({ "email": "zhangsan@example.com" })
// 不能 has 效利用index: 跳过了index  before 缀字段