<bdo id="ks4iu"><del id="ks4iu"></del></bdo>
  • 
    <pre id="ks4iu"></pre>
  • <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
    <input id="ks4iu"><em id="ks4iu"></em></input>
    
    
  • <center id="ks4iu"><cite id="ks4iu"></cite></center>
  • 首頁 > 空調(diào) >

    云上MongoDB常見索引問題及最優(yōu)索引規(guī)則大全

    背景

    騰訊云MongoDB當(dāng)前已服務(wù)于游戲、電商、社交、教育、新聞資訊、金融、物聯(lián)網(wǎng)、軟件服務(wù)、汽車出行、音視頻等多個(gè)行業(yè)。

    騰訊MongoDB團(tuán)隊(duì)在配合用戶分析問題過程中,發(fā)現(xiàn)云上用戶存在如下索引共性問題,主要集中在如下方面:

    無用索引 重復(fù)索引 索引不是最優(yōu) 對(duì)索引理解有誤等。

    本文重點(diǎn)分析總結(jié)騰訊云上用戶索引創(chuàng)建不合理相關(guān)的問題,通過本文可以學(xué)習(xí)到MongoDB的以下知識(shí)點(diǎn):

    如果理解MongoDB執(zhí)行計(jì)劃 如何確認(rèn)查詢索引是不是最優(yōu)索引 云上用戶對(duì)索引的一些錯(cuò)誤創(chuàng)建方法 如何創(chuàng)建最優(yōu)索引 創(chuàng)建最優(yōu)索引的規(guī)則匯總

    本文總結(jié)的《最優(yōu)索引規(guī)則創(chuàng)建大全》不僅僅適用于MongoDB,很多規(guī)則同樣適用于MySQL等關(guān)系型數(shù)據(jù)庫。

    MongoDB執(zhí)行計(jì)劃

    判斷索引選擇及不同索引執(zhí)行家伙信息可以通過explain操作獲取,MongoDB通過explain來獲取SQL執(zhí)行過程信息,當(dāng)前持續(xù)explain的請(qǐng)求命令包含以下幾種:

    aggregate, count, distinct, find, findAndModify, delete, mapReduce,and update。

    詳見explain官網(wǎng)連接:

    https://docs.MongoDB.com/manual/reference/command/explain/

    explain可以攜帶以下幾個(gè)參數(shù)信息,各參數(shù)信息功能如下:

    2.1.queryPlanner信息

    獲取MongoDB查詢優(yōu)化器選擇的最優(yōu)索引和拒絕掉的非最優(yōu)索引,并給出各個(gè)候選索引的執(zhí)行階段信息,queryPlanner輸出信息如下:

    cmgo-xxxx:PRIMARY> db.test4.find({xxxx}).explain("queryPlanner")

    {

    "queryPlanner" : {

    "parsedQuery" : {

    ......;//查詢條件對(duì)應(yīng)的expression Tree

    },

    "winningPlan" : {

    //查詢優(yōu)化器選擇的最優(yōu)索引及其該索引對(duì)應(yīng)的執(zhí)行階段信息

    ......;

    },

    "rejectedPlans" : [

    //查詢優(yōu)化器拒絕掉的非最優(yōu)索引及其該索引對(duì)應(yīng)的執(zhí)行階段信息

    ......;

    ]

    },

    }

    queryPlanner輸出主要包括如下信息:

    parsedQuery信息

    內(nèi)核對(duì)查詢條件進(jìn)行序列化,生成一棵expression tree信息,便于候選索引查詢匹配。

    winningPlan信息

    "winningPlan" : {

    "stage" : ,

    "inputStage" : {

    "stage" : ,

    "inputStage" : {

    "stage" : ,

    }

    }

    }

    winningPlan提供查詢優(yōu)化器選出的最優(yōu)索引及其查詢通過該索引的執(zhí)行階段信息,子stage傳遞該節(jié)點(diǎn)獲取的文檔或者索引信息給父stage,其輸出項(xiàng)中幾個(gè)重點(diǎn)字段需要關(guān)注:

    字段名

    功能說明

    stage

    表示SQL運(yùn)行所處階段信息,根據(jù)不同SQL及其不同候選索引,stage不同,常用stage字段包括以下幾種:

    COLLSCAN:該階段為掃表操作

    IXSCAN:索引掃描階段,表示查詢走了該索引

    FETCH:filter獲取滿足條件的doc

    SHARD_MERGE:分片集群,如果mongos獲取到多個(gè)分片的數(shù)據(jù),則聚合操作在該階段實(shí)現(xiàn)

    SHARDING_FILTER :filter獲取分片集群滿足條件的doc

    SORT:內(nèi)存排序階段

    OR:$orexpression類查詢對(duì)應(yīng)stage

    ……

    rejectedPlans信息

    輸出信息和winningPlan類似,記錄這些拒絕掉索引的執(zhí)行stage信息。

    2.2.executionStats信息

    explain的executionStats參數(shù)除了提供上面的queryPlanner信息外,還提供了最優(yōu)索引的執(zhí)行過程信息,如下:

    db.test4.find({xxxx}).explain("executionStats")

    "executionStats" : {

    "executionSuccess" : ,

    "nReturned" : ,

    "executionTimeMillis" : ,

    "totalKeysExamined" : ,

    "totalDocsExamined" : ,

    "executionStages" : {

    "stage" :

    "nReturned" : ,

    "executionTimeMillisEstimate" : ,

    "works" : ,

    "advanced" : ,

    "needTime" : ,

    "needYield" : ,

    "saveState" : ,

    "restoreState" : ,

    "isEOF" : ,

    "inputStage" : {

    "stage" : ,

    "nReturned" : ,

    "executionTimeMillisEstimate" : ,

    "inputStage" : {

    }

    }

    },

    }

    上面是通過executionStats獲取執(zhí)行過程的詳細(xì)信息,其中字段信息較多,平時(shí)分析索引問題最常用的幾個(gè)字段如下:

    字段名

    功能說明

    Stage

    Stage字段和queryPlanner信息中stage意思一致,用戶表示執(zhí)行計(jì)劃的階段信息

    nReturned

    本stage滿足查詢條件的數(shù)據(jù)索引數(shù)據(jù)或者doc數(shù)據(jù)條數(shù)

    executionTimeMillis

    整個(gè)查詢執(zhí)行時(shí)間

    totalKeysExamined

    索引key掃描行數(shù)

    totalDocsExamined

    Doc掃描行數(shù)

    executionTimeMillisEstimate

    本stage階段執(zhí)行時(shí)間

    executionStats輸出字段較多,其他字段將在后續(xù)《MongoDB內(nèi)核index索引模塊實(shí)現(xiàn)原理》中進(jìn)行進(jìn)一步說明。

    在實(shí)際分析索引問題是否最優(yōu)的時(shí)候,主要查看executionStats.totalKeysExamined、

    executionStats.totalDocsExamined、executionStats .nReturned三個(gè)統(tǒng)計(jì)項(xiàng),如果存在以下情況則說明索引存在問題,可能索引不是最優(yōu)的:

    executionStats.totalKeysExamine遠(yuǎn)大于executionStats .nReturned executionStats. totalDocsExamined遠(yuǎn)大于executionStats .nReturned

    2.3.allPlansExecution信息

    allPlansExecution參數(shù)對(duì)應(yīng)輸出信息和executionStats輸出信息類似,只是多了所有候選索引(包括reject拒絕的非最優(yōu)索引)的執(zhí)行過程,這里不在詳述。

    2.4.總結(jié)

    從上面的幾個(gè)explain執(zhí)行計(jì)劃參數(shù)輸出信息可以看出,各個(gè)參數(shù)功能各不相同,總結(jié)如下:

    queryPlanner

    輸出索引的候選索引,包括最優(yōu)索引及其執(zhí)行stage過程(winningPlan)+其他非最優(yōu)候選索引及其執(zhí)行stage過程。

    注意:queryPlanner沒有真正在表中執(zhí)行整個(gè)SQL,只做了查詢優(yōu)化器獲取候選索引過程,因此可以很快返回。

    executionStats

    相比queryPlanner參數(shù),executionStats會(huì)記錄查詢優(yōu)化器根據(jù)所選最優(yōu)索引執(zhí)行SQL的整個(gè)過程信息,會(huì)真正執(zhí)行整個(gè)SQL。

    allPlansExecution

    和executionStats類似,只是多了所有候選索引的執(zhí)行過程。

    云上用戶建索引常見問題及優(yōu)化方法

    在和用戶一起優(yōu)化騰訊云上MongoDB集群索引過程中,通過和頭部用戶的交流過程中,發(fā)現(xiàn)很多用戶對(duì)如何創(chuàng)建最優(yōu)索引有較驗(yàn)證的錯(cuò)誤認(rèn)識(shí),并且很多是大部分用戶的共性問題,這些問題總結(jié)匯總?cè)缦拢?/p>

    3.1.等值類查詢常見索引錯(cuò)誤創(chuàng)建方法及如何創(chuàng)建最優(yōu)索引

    3.1.1. 同一類查詢創(chuàng)建多個(gè)索引問題

    如下三個(gè)查詢:

    db.test4.find({"a":"xxx", "b":"xxx", "c":"xxx"})

    db.test4.find({"b":"xxx", "a":"xxx", "c":"xxx"})

    db.test4.find({"c":"xxx", "a":"xxx", "b":"xxx"})

    用戶創(chuàng)建了如下3個(gè)索引:

    {a:1, b:1, c:1}

    {b:1, a:1, c:1}

    {c:1, a:1, b:1}

    實(shí)際上這3個(gè)查詢屬于同一類查詢,只是查詢字段順序不一樣,因此只需創(chuàng)建任一個(gè)索引即可滿足要求。驗(yàn)證過程如下:

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"a" : 1, "b" : 1, "c" : 1}).explain("executionStats").queryPlanner.winningPlan

    {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_1",

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"b" : 1, "a" : 1, "c" : 1}).explain("executionStats").queryPlanner.winningPlan

    {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_1",

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"c" : 1, "a" : 1, "b" : 1}).explain("executionStats").queryPlanner.winningPlan

    {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_1",

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY>

    從上面的expalin輸出可以看出,3個(gè)查詢都走了同一個(gè)索引。

    3.1.2. 多字段等值查詢組合索引順序非最優(yōu)

    例如test表有多條數(shù)據(jù),每條數(shù)據(jù)有3個(gè)字段,分別為a、b、c。其中a字段有10種取值,b字段有100種取值,c字段有1000種取值,稱為各個(gè)字段值的“區(qū)分度”。

    用戶查詢條件為db.test.find({"a":"xxx", "b":"xxx", "c":"xxx"}),創(chuàng)建的索引為{a:1, b:1, c:1}。如果只是針對(duì)這個(gè)查詢,該查詢可以創(chuàng)建a,b,c三字段的任意組合,并且其SQL執(zhí)行代價(jià)一樣,通過hint強(qiáng)制走不通索引,驗(yàn)證過程如下:

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"a" : 1, "b" : 1, "c" : 1}).hint({"a" : 1, b:1, c:1}).explain("executionStats").executionStats

    {

    "nReturned" : 1,

    "executionTimeMillis" : 0,

    "totalKeysExamined" : 1,

    "totalDocsExamined" : 1,

    "executionStages" : {

    "stage" : "FETCH",

    "nReturned" : 1,

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_c_1_b_1",

    }

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"a" : 1, "b" : 1, "c" : 1}).hint({"a" : 1, c:1, b:1}).explain("executionStats").executionStats

    {

    "nReturned" : 1,

    "executionTimeMillis" : 0,

    "totalKeysExamined" : 1,

    "totalDocsExamined" : 1,

    "executionStages" : {

    "stage" : "FETCH",

    "nReturned" : 1,

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_c_1_b_1",

    }

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"c" : 1, "a" : 1, "b" : 1}).hint({"a" : 1, c:1, b:1}).explain("executionStats").executionStats

    {

    "nReturned" : 1,

    "executionTimeMillis" : 0,

    "totalKeysExamined" : 1,

    "totalDocsExamined" : 1,

    "executionStages" : {

    "stage" : "FETCH",

    "nReturned" : 1,

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_c_1_b_1",

    }

    }

    }

    從上面的執(zhí)行計(jì)劃可以看出,多字段等值查詢各個(gè)字段的組合順序?qū)?yīng)執(zhí)行計(jì)劃代價(jià)一樣。絕大部分用戶在創(chuàng)建索引的時(shí)候,都是直接按照查詢字段索引組合對(duì)應(yīng)字段。

    但是,單就這一個(gè)查詢,這里有個(gè)不成文的建議,把區(qū)分度更高的字段放在組合索引左邊,區(qū)分度低的字段放到右邊。這樣做有個(gè)好處,數(shù)據(jù)庫組合索引遵從最左原則,就是當(dāng)其他查詢里面帶有區(qū)分度最高的字段時(shí),就可以快速排除掉更多不滿足條件的數(shù)據(jù)。

    3.1.3. 最左原則包含關(guān)系引起的重復(fù)索引

    例如用戶有如下兩個(gè)查詢:

    db.test.find({"b" : 2, "c" : 1}) //查詢1

    db.test.find({"a" : 10, "b" : 5, "c" : 1}) //查詢2

    用戶創(chuàng)建了如下兩個(gè)索引:

    {b:1, c:1}

    {a:1,b:1,c:1}

    這兩個(gè)查詢中,查詢2中包含有查詢1中的字段,因此可以用一個(gè)索引來滿足這兩個(gè)查詢要求,按照最左原則,查詢1字段放左邊即可,該索引可以優(yōu)化為:b, c字段索引+a字段索引,b,c字段順序可以根據(jù)區(qū)分排序,加上c字段區(qū)分度比b高,則這兩個(gè)查詢可以合并為一個(gè){c:1, b:1, a:1}。兩個(gè)查詢可以走同一個(gè)索引驗(yàn)證過程如下:

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"b" : 2, "c" : 1}).explain("executionStats")

    {

    "winningPlan" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "c_1_b_1_a_1",

    }

    }

    }

    MongoDB_4.4_shard2:PRIMARY>

    MongoDB_4.4_shard2:PRIMARY> db.test.find({"a" : 10, "b" : 5, "c" : 1}).explain("executionStats")

    {

    "winningPlan" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "c_1_b_1_a_1",

    }

    }

    }

    從上面輸出可以看出,這兩個(gè)查詢都走了同一個(gè)索引。

    3.1.4. 唯一字段和其他字段組合引起的無用重復(fù)索引

    例如用戶有以下兩個(gè)查詢:

    db.test.find({a:1,b:1})

    db.test.find({a:1,c:1})

    用戶為這兩個(gè)查詢創(chuàng)建了兩個(gè)索引,{a:1, b:1}和{a:1, c:1},但是a字段取值是唯一的,因此這兩個(gè)查詢中a以外的字段無用,一個(gè){a:1}索引即可滿足要求。

    3.2.非等值類查詢常見索引錯(cuò)誤創(chuàng)建方法及如何創(chuàng)建最優(yōu)索引

    3.2.1. 非等值組合查詢索引不合理創(chuàng)建

    假設(shè)用戶有如下查詢:

    //兩字段非等值查詢

    db.test.find({a:{$gte:1}, c:{$lte:1}})

    a,c兩個(gè)字段都是非等值查詢,很多用戶直接添加了{(lán)a:1, c:1}索引,實(shí)際上多個(gè)字段的非等值查詢,只有最左邊的字段才能走索引,例如這里只會(huì)走a字段索引,驗(yàn)證過程如下:

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({a:{$gte:1}, c:{$lte:1}}).explain("executionStats")

    {

    "executionStats" : {

    "nReturned" : 4,

    "executionTimeMillis" : 0,

    "totalKeysExamined" : 10,

    "totalDocsExamined" : 4,

    "inputStage" : {

    "indexName" : "a_1_c_1",

    }

    }

    從上面執(zhí)行計(jì)劃可以看出,索引數(shù)據(jù)掃描了10行(也就是a字段滿足a:{$gte:1}條件的數(shù)據(jù)多少),但是實(shí)際上只返回了4條滿足{a:{$gte:1}, c:{$lte:1}}條件的數(shù)據(jù),可以看出c字段無法走索引。

    同理,當(dāng)查詢中包含多個(gè)字段的范圍查詢的適合,除了最左邊第一個(gè)字段可以走索引,其他字段都無法走索引。因此,上面例子中的查詢候選索引為{a:1}或者{b:1}中任何一個(gè)就可以了,組合索引中字段太多會(huì)占用更多存儲(chǔ)成本、同時(shí)暫用更多IO資源引起寫放大。

    3.2.2. 等值+非等值組合查詢索引字段順序不合理

    例如下面查詢:

    //兩字段非等值查詢

    db.test.find({"d":{$gte:4}, "e":1})

    如上查詢,d字段為非等值查詢,e字段為等值查詢,很多用戶遇到該類查詢直接創(chuàng)建了{(lán)d:1, e:1}索引,由于d字段為非等值查詢,因此e字段無法走索引,驗(yàn)證過程如下:

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({"d":{$gte:4}, "e":1}).hint({d:1, e:1}).explain("executionStats")

    {

    "executionStats" : {

    ……

    "totalKeysExamined" : 5,

    "totalDocsExamined" : 3,

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "d_1_e_1",

    }

    }

    MongoDB_4.4_shard1:PRIMARY> db.test.find({"d":{$gte:4}, "e":1}).hint({e:1, d:1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 3,

    "totalDocsExamined" : 3,

    "inputStage" : {

    "indexName" : "e_1_d_1",

    }

    從上面驗(yàn)證過程可以看出,等值類和非等值類組合查詢對(duì)應(yīng)組合索引,最優(yōu)索引應(yīng)該優(yōu)先把等值查詢放到左邊,上面查詢對(duì)應(yīng)最優(yōu)索引{e:1, d:1}。

    3.2.3. 不同類型非等值查詢優(yōu)先級(jí)問題

    前面用到的非等值查詢操作符只提到了比較類操作符,實(shí)際上非等值查詢還有其他操作符。常用非等值查詢包括:$gt、$gte、$lt、$lte、$in、$nin、$ne、$exists、$type等,這些非等值查詢?cè)诮^大部分情況下存在如下優(yōu)先級(jí):

    $In $gt $gte $lt $lte $nin $ne $type $exist

    從上到下優(yōu)先級(jí)更高,例如下面的查詢:

    //等值+多個(gè)不同優(yōu)先級(jí)非等值查詢

    db.test.find({"a":1, "b":1, "c":{$ne:5}, "e":{$type:"string"}, "f":{$gt:5},"g":{$in:[3,4]}) 查詢1

    如上,該查詢等值部分查詢最優(yōu)索引{a:1,b:1}(假設(shè)a區(qū)分度比b高);非等值部分,因?yàn)?in操作符優(yōu)先級(jí)最高,排他性更好,加上多個(gè)字段非等值查詢只會(huì)有一個(gè)字段走索引,因此非等值部分最優(yōu)索引為{g:1}。

    最終該查詢最優(yōu)索引為:”等值部分最優(yōu)索引”與”非等值部分最優(yōu)索引”拼接,也就是{a:1,b:1, g:1}。

    3.3.OR類查詢常見索引錯(cuò)誤創(chuàng)建方法及如何創(chuàng)建最優(yōu)索引

    3.3.1. 普通OR類查詢

    例如如下or查詢:

    //or中包含兩個(gè)查詢

    db.test.find( { $or: [{ b: 0,d:0 }, {"c":1, "a":{$gte:4}} ] } )

    該查詢很多用戶直接創(chuàng)建了{(lán)b:1,d:1, c:1, a:1},用戶創(chuàng)建該索引后,發(fā)現(xiàn)用戶還是全表掃描。

    Or類查詢需要給數(shù)組中每個(gè)查詢添加索引,例如上面or數(shù)組中實(shí)際包含{ b: 0, d:0 }和{"c":1, "a":{$gte:4}}查詢,需要?jiǎng)?chuàng)建兩個(gè)查詢的最優(yōu)索引,也就是{b:1, d:1}和{c:1, a:1},執(zhí)行計(jì)劃驗(yàn)證過程如下(該測(cè)試表總10條數(shù)據(jù)):

    MongoDB_4.4_shard1:PRIMARY> db.test.find( { $or: [{ b: 0,d:0 }, {"c":1, "a":{$gte:4}}]}).hint({b:1, d:1, c:1, a:1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 10,

    "totalDocsExamined" : 10,

    "inputStage" : {

    "indexName" : "b_1_d_1_c_1_a_1",

    }

    }

    //創(chuàng)建{b:1,d:1}和{c:1, a:1}兩個(gè)索引后,優(yōu)化器選擇這兩個(gè)索引做為最優(yōu)索引

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find( { $or: [{ b: 0,d:0 }, {"c":1, "a":{$gte:4}}]}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 2,

    "totalDocsExamined" : 2,

    "executionStages" : {

    "stage" : "SUBPLAN",

    "inputStage" : {

    "stage" : "OR",

    "inputStages" : [

    {

    "stage" : "IXSCAN",

    "indexName" : "b_1_d_1",

    },

    {

    "stage" : "IXSCAN",

    "indexName" : "c_1_a_1",

    }

    ]

    } }

    }

    },

    從上面執(zhí)行計(jì)劃可以看出,如果該OR類查詢走{b:1, d:1, c:1, a:1}索引,則實(shí)際上做了全表掃描。如果同時(shí)創(chuàng)建{b:1, d:1}、{c:1, a:1}索引,則直接走兩個(gè)索引,其執(zhí)行key和doc掃描行數(shù)遠(yuǎn)遠(yuǎn)小于全表掃描。

    3.3.2. 復(fù)雜OR類查詢

    這里在提升一下OR查詢難度,例如下面的查詢:

    //等值查詢+or類查詢+sort排序查詢

    db.test.find( {"f":3, g:2, $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ) 查詢1

    上面的查詢可以轉(zhuǎn)換為如下兩個(gè)查詢:

    ------db.test.find( {"f":3, g:2, b: 0, d:0 } ) //查詢2

    or--|

    ------db.test.find( {"f":3, g:2, "c":1, "a":6} ) //查詢3

    如上圖,查詢1拆分后的兩個(gè)查詢2和查詢3組成or關(guān)系,因此對(duì)應(yīng)最優(yōu)所有需要?jiǎng)?chuàng)建兩個(gè),分表是:{f:1, g:1, b:1, d:1}和 {f:1, g:1, b:1, d:1}。對(duì)應(yīng)執(zhí)行計(jì)劃如下:

    MongoDB_4.4_shard1:PRIMARY> db.test.find( {"f":3, g:2, $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 7,

    "totalDocsExamined" : 7,

    "executionStages" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "OR",

    "inputStages" : [

    {

    "stage" : "IXSCAN",

    "indexName" : "f_1_g_1_c_1_a_1",

    },

    {

    "stage" : "IXSCAN",

    "indexName" : "f_1_g_1_b_1_d_1",

    }

    ]

    }

    }

    },

    }

    同理,不管怎么增加難度,OR查詢最終可轉(zhuǎn)換為多個(gè)等值、非等值或者等值與非等值組合類查詢,通過如上變換最終可以做到舉一反三的作用。

    說明:這個(gè)例子中可能在一些特殊數(shù)據(jù)分布場(chǎng)景,最優(yōu)索引也可能是{f:1, g:1}或者{f:1, g:1, b:1, d:-1}或者{ f:1, g:1, c:1, a:1},這里我們只考慮大部分通用場(chǎng)景。

    3.4.Sort類排序查詢常見索引錯(cuò)誤創(chuàng)建方法及如何創(chuàng)建最優(yōu)索引

    3.4.1. 單字段正反序排序查詢引起的重復(fù)索引

    例如用戶有以下兩個(gè)查詢:

    db.test.find({}).sort({a:1}).limit(2)

    db.test.find({}).sort({a:-1}).limit(2)

    這兩個(gè)查詢都不帶條件,排序方式不一樣,因此很多創(chuàng)建了兩個(gè)索引{a:1}和{a:-1},實(shí)際上這兩個(gè)索引中的任何一個(gè)都可以滿足兩種查詢要求,驗(yàn)證過程如下:

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({}).sort({a:1}).limit(2).explain("executionStats")

    {

    "winningPlan" : {

    "stage" : "LIMIT",

    "limitAmount" : 2,

    "inputStage" : {

    "indexName" : "a_1",

    }

    }

    },

    }

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({}).sort({a:-1}).limit(2).explain("executionStats")

    {

    "winningPlan" : {

    "stage" : "LIMIT",

    "limitAmount" : 2,

    "inputStage" : {

    "indexName" : "a_1",

    }

    }

    },

    },

    3.4.2. 多字段排序查詢正反序問題引起索引無效

    假設(shè)有如下查詢:

    //兩字段排序查詢

    db.test.find().sort({a:1, b:-1}).limit(5)

    其中a字段為正序,b字段為反序排序,很多用戶直接創(chuàng)建{a:1, b:1}索引,這時(shí)候b字段內(nèi)容就存在內(nèi)存排序情況。多字段排序索引,如果沒有攜帶查詢條件,則最優(yōu)索引即為排序字段對(duì)應(yīng)索引,這里切記保持每個(gè)字段得正反序和sort完全一致,否則可能存在部分字段內(nèi)存排序的情況,執(zhí)行計(jì)劃驗(yàn)證過程如下:

    //{a:1, b:1}只會(huì)有一個(gè)字段走索引,另一個(gè)字段內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find().sort({a:1, b:-1}).hint({a:1, b:1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 15,

    "totalDocsExamined" : 15,

    "inputStage" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "SORT",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1",

    }

    }

    }

    }

    },

    //{a:1, b:-1}兩個(gè)字段走索引,不存在內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find().sort({a:1, b:-1}).hint({a:1, b:-1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 15,

    "totalDocsExamined" : 15,

    "inputStage" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_-1",

    }

    }

    }

    },

    }

    3.4.3. 等值查詢+多字段排序組合查詢

    例如如下查詢:

    //多字段等值查詢+多字段排序查詢

    db.test.find({ "a" : 3, "b" : 1}).sort({c:-1, d:1})

    該類查詢很多人直接創(chuàng)建{a:1,b:1, c:1, d:1},結(jié)果造成內(nèi)存排序。這種組合查詢最優(yōu)索引=“多字段等值查詢最優(yōu)索引_多字段排序類組合最優(yōu)索引”,例如該查詢:

    { "a" : 3, "b" : 1}等值查詢假設(shè)a區(qū)分度比b高,則對(duì)應(yīng)最優(yōu)索引為:{a:1, b:1}

    { c:-1, d:1}排序類查詢最優(yōu)索引保持正反序一致,也就是:{ c:-1, d:1}

    因此整個(gè)查詢就是這兩個(gè)查詢對(duì)應(yīng)最優(yōu)索引拼接,也就是{a:1, b:1, c:-1, d:1},對(duì)應(yīng)執(zhí)行計(jì)劃過程驗(yàn)證如下:

    //非最優(yōu)索引執(zhí)行計(jì)劃,存在內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({ "a" : 3, "b" : 1}).sort({c:-1, d:1}).hint({a:1, b:1, c:1, d:1}).explain("executionStats")

    {

    "executionStats" : {

    "executionStages" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "SORT",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_1_d_1",

    }

    }

    }

    },

    }

    //最優(yōu)索引執(zhí)行計(jì)劃,直接走排序索引

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find({ "a" : 3, "b" : 1}).sort({c:-1, d:1}).hint({a:1, b:1, c:-1, d:1}).explain("executionStats")

    {

    "executionStats" : {

    "executionStages" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_-1_d_1",

    }

    }

    },

    }

    3.4.4. 等值查詢+非等值查詢+sort排序查詢

    假設(shè)有下面的查詢:

    //等值+非等值+sort排序查詢

    db.test.find({"a":3, "b":1, "c":{$gte:1}}).sort({d:-1, e:1})

    騰訊云很多用戶看到該查詢直接創(chuàng)建{a:1,b:1, c:1, d:-1, e:1}索引,發(fā)現(xiàn)存在內(nèi)存排序。等值+非等值+sort排序組合查詢,由于非等值查詢右邊的字段不能走索引,因此如果把d, e放到c的右邊,則d,e字段索引無效。

    等值+非等值+sort排序最優(yōu)索引組合字段順序?yàn)椋旱戎礯sort排序_非等值,因此上面查詢最優(yōu)索引為:{a:1, b:1, d:-1, e:1, c:1}。執(zhí)行計(jì)劃驗(yàn)證過程如下:

    //走部分索引,然后內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY> db.test.find({"a":3, "b":1, "c":{$gte:1}}).sort({d:-1, e:1}).hint({"a":1, b:1, c:1, d:-1, e:1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 9,

    "totalDocsExamined" : 9,

    "executionStages" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "SORT", //內(nèi)存排序

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_c_1_d_-1_e_1",

    }

    }

    }

    },

    }

    //直接走排序索引

    MongoDB_4.4_shard1:PRIMARY> db.test.find({"a":3, "b":1, "c":{$gte:1}}).sort({d:-1, e:1}).hint({"a":1, b:1, d:-1, e:1, c:1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 10,

    "totalDocsExamined" : 9,

    "executionStages" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "a_1_b_1_d_-1_e_1_c_1",

    }

    }

    },

    }

    3.4.5. OR +SORT組合排序查詢

    例如如下查詢:

    //or+sort組合 查詢1

    db.test.find( { $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ).sort({e:-1})

    上面組合很多人直接創(chuàng)建{b:1, d:1, c:1, a:1, e:1},該索引創(chuàng)建后還是會(huì)掃表和內(nèi)存排序,實(shí)際上OR+SORT組合查詢可以轉(zhuǎn)換為下面兩個(gè)查詢:

    //查詢1等價(jià)轉(zhuǎn)換為如下查詢

    -----db.test.find({ b: 3, d:5 }).sort({e:-1}) //查詢2

    or--|

    -----db.test.find( {"c":1, "a":6} ).sort({e:-1}) //查詢3

    所以這個(gè)復(fù)雜查詢就可以拆分為等值組合查詢+sort排序查詢,拆分為上面的兩個(gè)查詢,這樣我們只需要同時(shí)創(chuàng)建查詢2和查詢3對(duì)應(yīng)最優(yōu)索引即可。該查詢最終拆分后對(duì)應(yīng)最優(yōu)索引需要添加如下兩個(gè):

    {b:1, d:1, e:-1}和{c:1,a:1, e:-1}

    非最優(yōu)索引和最優(yōu)索引執(zhí)行計(jì)劃驗(yàn)證過程如下:

    //走{b:1, d:1, c:1, a:1, e:-1}索引,全表掃描加內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find( { $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ).sort({e:-1}).hint({b:1, d:1, c:1, a:1, e:-1}).explain("executionStats")

    {

    "executionStats" : {

    //測(cè)試構(gòu)造表中23條數(shù)據(jù),總數(shù)據(jù)23條

    "totalKeysExamined" : 23,

    "totalDocsExamined" : 23,

    "executionStages" : {

    "stage" : "SORT",

    "inputStage" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "IXSCAN",

    "indexName" : "b_1_d_1_c_1_a_1_e_-1",

    }

    }

    }

    },

    }

    //走{b:1, d:1, e:-1}和{c:1, a:1, e:-1}兩個(gè)最優(yōu)索引的執(zhí)行計(jì)劃,無內(nèi)存排序

    MongoDB_4.4_shard1:PRIMARY>

    MongoDB_4.4_shard1:PRIMARY> db.test.find( { $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ).sort({e:-1}).explain("executionStats")

    {

    "executionStats" : {

    "totalKeysExamined" : 2,

    "totalDocsExamined" : 2,

    "inputStage" : {

    "stage" : "FETCH",

    "inputStage" : {

    "stage" : "SORT_MERGE",

    "inputStages" : [

    {

    "stage" : "IXSCAN",

    "indexName" : "b_1_d_1_e_1",

    },

    {

    "stage" : "IXSCAN",

    "indexName" : "c_1_a_1_e_1",

    }

    ]

    }

    }

    }

    },

    }

    OR+SORT類查詢,最終可以《參考前面的OR類查詢常見索引錯(cuò)誤創(chuàng)建方法》把OR查詢轉(zhuǎn)換為多個(gè)等值、非等值或者等值與非等值組合查詢,然后與sort排序?qū)?yīng)索引字段拼接。例如下面查詢:

    //原查詢

    db.test.find( {"f":3, g:2, $or: [{ b: 0, d:0 }, {"c":1, "a":6} ] } ).sort({e:-1}) //查詢1

    拆分后的兩個(gè)查詢組成or關(guān)系,如下:

    //拆分后查詢

    ------ db.test.find( {"f":3, g:2, b: 0, d:0} ).sort({e:-1}) //查詢2

    or---

    ------ db.test.find( {"f":3, g:2, "c":1, "a":6}).sort({e:-1}) //查詢3

    如上,查詢1 = or: [查詢2, 查詢3],因此只需要?jiǎng)?chuàng)建查詢2和查詢3兩個(gè)最優(yōu)索引即可滿足查詢1要求,查詢2和查詢3最優(yōu)索引可以參考前面《or類查詢常見索引錯(cuò)誤創(chuàng)建方法》,該查詢最終需要?jiǎng)?chuàng)建如下兩個(gè)索引:

    {f:1, g:1, b:1, d:1, e:-1}和{ f:1, g:1, c:1, a:1, e:-1}

    說明:這個(gè)例子中可能在一些特殊數(shù)據(jù)分布場(chǎng)景,最優(yōu)索引也可能是{f:1, g:1}或者{f:1, g:1, b:1, d:1, e:-1}或者{ f:1, g:1, c:1, a:1, e:-1},這里我們只考慮通用場(chǎng)景。

    3.5.避免創(chuàng)建太多無用索引及無用索引分析方法

    在騰訊云上,我們還發(fā)現(xiàn)另外一個(gè)問題,很多實(shí)例存在大量無用索引,無用索引會(huì)引起以下問題:

    存儲(chǔ)成本增加

    沒增加一個(gè)索引,MongoDB內(nèi)核就會(huì)創(chuàng)建一個(gè)index索引文件,記錄該表的索引數(shù)據(jù),造成存儲(chǔ)成本增加。

    影響寫性能

    用戶沒寫入一條數(shù)據(jù),就會(huì)在對(duì)應(yīng)索引生成一條索引KV,實(shí)現(xiàn)索引與數(shù)據(jù)的一一對(duì)應(yīng),索引KV數(shù)據(jù)寫入Index索引文件過程加劇寫入負(fù)載。

    影響讀性能

    MongoDB內(nèi)核查詢優(yōu)化器原理是通過候選索引快速定位到滿足條件的數(shù)據(jù),然后采樣評(píng)分。如果滿足條件的候選索引越多,整個(gè)評(píng)分過程就會(huì)越長,增加內(nèi)核選擇最優(yōu)索引的流程。

    下面已一個(gè)真實(shí)線上實(shí)例為例,說明如何找出無用索引:

    db.xxx.aggregate({"$indexStats":{}})

    { "alxxxId" : 1, "state" : -1, "updateTime" : -1, "itxxxId" : -1, "persxxal" : 1, "srcItxxxId" : -1 } "ops" : NumberLong(88518502)

    { "alxxxId" : 1, "image" : 1 } "ops" : NumberLong(293104)

    { "itexxxList.vidxxCheck" : 1, "itemType" : 1, "state" : 1 } "ops" : NumberLong(0)

    { "alxxxId" : 1, "state" : -1, "newsendTime" : -1, "itxxxId" : -1, "persxxal" : 1 } "ops" : NumberLong(33361216)

    { "_id" : 1 } "ops" : NumberLong(3987)

    { "alxxxId" : 1, "createTime" : 1, "checkStatus" : 1 } "ops" : NumberLong(20042796)

    { "alxxxId" : 1, "parentItxxxId" : -1, "state" : -1, "updateTime" : -1, "persxxal" : 1, "srcItxxxId" : -1 } "ops" : NumberLong(43042796)

    { "alxxxId" : 1, "state" : -1, "parentItxxxId" : 1, "updateTime" : -1, "persxxal" : -1 } "ops" : NumberLong(3042796)

    { "itxxxId" : -1} "ops" : NumberLong(38854593)

    { "srcItxxxId" : -1 } "ops" : NumberLong(0)

    { "createTime" : 1 } "ops" : NumberLong(62)

    { "itexxxList.boyunState" : -1, "itexxxList.wozhituUploadServerId" : -1, "itexxxList.photoQiniuUrl" : 1, "itexxxList.sourceType" : 1 } "ops" : NumberLong(0)

    { "alxxxId" : 1, "state" : 1, "digitalxxxrmarkId" : 1, "updateTime" : -1 } "ops" : NumberLong(140238342)

    { "itxxxId" : -1 } "ops" : NumberLong(38854593)

    { "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 } "ops" : NumberLong(132237254)

    { "alxxxId" : 1, "videoCover" : 1 } { "ops" : NumberLong(2921857)

    { "alxxxId" : 1, "itemType" : 1 } { "ops" : NumberLong(457)

    { "alxxxId" : 1, "state" : -1, "itemType" : 1, "persxxal" : 1, " itxxxId " : 1 } "ops" : NumberLong(68730734)

    { "alxxxId" : 1, "itxxxId" : 1 } "ops" : NumberLong(232360252)

    { "itxxxId" : 1, "alxxxId" : 1 } "ops" : NumberLong(145640252)

    { "alxxxId" : 1, "parentAlxxxId" : 1, "state" : 1 } "ops" : NumberLong(689891)

    { "alxxxId" : 1, "itemTagList" : 1 } "ops" : NumberLong(2898693682)

    { "itexxxList.photoQiniuUrl" : 1, "itexxxList.boyunState" : 1, "itexxxList.sourceType" : 1, "itexxxList.wozhituUploadServerId" : 1 } "ops" : NumberLong(511303207)

    { "alxxxId" : 1, "parentItxxxId" : 1, "state" : 1 } "ops" : NumberLong(0)

    { "alxxxId" : 1, "parentItxxxId" : 1, "updateTime" : 1 } "ops" : NumberLong(0)

    { "updateTime" : 1 } "ops" : NumberLong(1397)

    { "itemPhoxxIdList" : -1 } "ops" : NumberLong(0)

    { "alxxxId" : 1, "state" : -1, "isTop" : 1 } "ops" : NumberLong(213305)

    { "alxxxId" : 1, "state" : 1, "itemResxxxIdList" : 1, "updateTime" : 1 } "ops" : NumberLong(2591780)

    { "alxxxId" : 1, "state" : 1, "itexxxList.photoQiniuUrl" : 1} "ops" : NumberLong(23505)

    { "itexxxList.qiniuStatus" : 1, "itexxxList.photoNetUrl" : 1, "itexxxList.photoQiniuUrl" : 1 } "ops" : NumberLong(0)

    { "itemResxxxIdList" : 1 } "ops" :NumberLong(7)

    MongoDB默認(rèn)提供有索引統(tǒng)計(jì)命令來獲取各個(gè)索引命中的次數(shù),該命令如下:

    > db.xxxxx.aggregate({"$indexStats":{}})

    { "name" : "alxxxId_1_parentItxxxId_1_parentAlxxxId_1", "key" : { "alxxxId" : 1, "parentItxxxId" : 1, "parentAlxxxId" : 1 }, "host" : "TENCENT64.site:7014", "accesses" : { "ops" : NumberLong(11236765), "since" : ISODate("2020-08-17T06:39:43.840Z") } }

    該聚合輸出中的幾個(gè)核心指標(biāo)信息如下表:

    字段內(nèi)容

    說明

    name

    索引名,代表是針對(duì)那個(gè)索引的統(tǒng)計(jì)。

    ops

    索引命中次數(shù),也就是所有查詢中采用本索引作為查詢索引的次數(shù)。

    上表中的ops代表命中次數(shù),如果命中次數(shù)為0或者很小,說明該索引很少被選為最優(yōu)索引使用,因此可以認(rèn)為是無用索引,可以考慮刪除。

    MongoDB不同分類查詢最優(yōu)索引總結(jié)

    查詢大類

    子類

    生成候選索引規(guī)則

    普通查詢

    單字段查詢

    無需計(jì)算,直接輸出索引

    多字段等值查詢

    分析字段schema,得出區(qū)分度 如果某字段區(qū)分度和采樣數(shù)據(jù)條數(shù)一致,則直接添加該字段的索引即可,無需多字段組合,流程結(jié)束。 給出候選索引,按照區(qū)分度從左向右生成組合索引。 多字段等值查詢,只會(huì)有一個(gè)候選索引

    說明:本身多字段等值查詢,最優(yōu)索引和字段組合順序無關(guān),但是這里一般有個(gè)不成文歸檔,把區(qū)分度最高的字段放在最左邊,這樣有利于帶有該字段新查詢的快速排他性

    多字段非等值查詢

    非等值查詢,通過優(yōu)先級(jí)確定候選索引,非等值操作符優(yōu)先級(jí)順序如下:

    $In $gt $gte $lt $lte $nin $ne $type $exist

    如果字段優(yōu)先級(jí)一樣,則會(huì)對(duì)應(yīng)多個(gè)候選索引,例如:{a>1, b>1,c >1}查詢,候選索引是以下3個(gè)中的一個(gè):

    {a:1} {b:1} {c: 1}

    這時(shí)候就需要根據(jù)數(shù)據(jù)分布評(píng)估3個(gè)候選索引中那個(gè)更好。

    等值與非等值組合

    等值與非等值組合,候選索引規(guī)則步驟如下:

    等值按照schema區(qū)分度,獲取所有等值字段的候選索引,只會(huì)有一個(gè)候選索引 等值部分與所有非等值字段組合為候選索引,最終有多少個(gè)非等值查詢,就會(huì)有多少個(gè)候選索引

    舉例:db.collection.find(a=1, b=2, c>3, d>4)

    假設(shè)(a=1, b=2)等值查詢按照區(qū)分度最優(yōu)索引為{b:1,a:1},則候選索引有如下兩種:

    {b:1,a:1,c:1}

    {b:1,a:1,d:1}

    這時(shí)候就需要根據(jù)數(shù)據(jù)分布情況決定加這兩個(gè)候選索引的哪一個(gè)作為最優(yōu)索引。

    排序類型

    不帶查詢的排序

    不帶查詢條件的排序,

    例如:db.xx.find({}).sort({a:1,b:-1,c:1}),對(duì)應(yīng)候選索引直接是排序索引:

    {a:1,b:-1,c:1}

    普通查詢+sort排序

    該場(chǎng)景候選索引包括:

    等值查詢候選索引 Sort排序候選索引

    舉例:db.collection.find(a=1, b=2, c>3, d>4).sort({e:1, f:-1}),該查詢候選索引:

    等值查詢候選索引

    {b:1,a:1}

    {a:1,b:1}

    非等值部分候選索引

    {c:1}

    {d:1}

    Sort候選索引

    { e:1, f:-1}

    假設(shè)等值部分按照區(qū)分度最優(yōu)索引為{a:1, b:1},非等值最優(yōu)索引為{d:1},則整個(gè)查詢最優(yōu)索引=等值部分最優(yōu)索引_sort排序最優(yōu)索引_非等值部分最優(yōu)索引,也就是{a:1,b:1,e:1,f:-1d:1}

    OR類查詢

    (可拆分為多個(gè)普通查詢)

    一個(gè)子tree

    候選索引就是該子tree對(duì)應(yīng)候選索引,參考《普通查詢》對(duì)應(yīng)候選索引推薦算法

    多個(gè)子tree

    (無交集字段)

    對(duì)每個(gè)tree對(duì)應(yīng)普通查詢生成一個(gè)最優(yōu)索引,多個(gè)子tree會(huì)有多個(gè)候選索引,每個(gè)tree對(duì)應(yīng)候選索引規(guī)則參考《普通查詢》

    更多查詢匯總信息

    參考第三章

    參考第三章

    說明:

    本文總結(jié)的《最優(yōu)索引規(guī)則大全》中的規(guī)則適用于絕大部分查詢場(chǎng)景,但是一些特殊數(shù)據(jù)分布場(chǎng)景可能會(huì)有一定偏差,請(qǐng)根據(jù)實(shí)際數(shù)據(jù)分布進(jìn)行查詢計(jì)劃分析。

    責(zé)任編輯:Rex_08

    關(guān)鍵詞: stage MongoDB
    推薦閱讀
    欧美国产在线一区,免费看成年视频网页,国产亚洲福利精品一区,亚洲一区二区约美女探花
    <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
  • 
    <pre id="ks4iu"></pre>
  • <bdo id="ks4iu"><del id="ks4iu"></del></bdo>
    <input id="ks4iu"><em id="ks4iu"></em></input>
    
    
  • <center id="ks4iu"><cite id="ks4iu"></cite></center>
  • 主站蜘蛛池模板: 成年女人18级毛片毛片免费 | 四虎成年永久免费网站| 七次郎最新首页在线视频| 知乎的小说哪里可以免费| 在线观看的免费视频网站| 亚洲国产精品无码久久一区二区 | 国产午夜无码片在线观看| 中文字幕精品一区二区精品| 精品xxxxxbbbb欧美中文| 国内自产少妇自拍区免费| 亚洲一级毛片视频| 蜜柚视频网在线观看免费版| 妖精的尾巴ova| 亚洲欧洲日产国码av系列天堂| 韩国福利一区二区美女视频 | japan高清日本乱xxxxx| 欧美日韩国产综合视频在线看| 国产成人亚洲综合在线| 两根黑人粗大噗嗤噗嗤视频| 琪琪色原网站在线观看| 国产精品一区二区三区久久| 久久中文字幕2021精品| 狠狠色噜噜狠狠狠狠av| 国产男女猛烈无遮挡免费视频| 中文字幕日韩亚洲| 欧美精品免费观看二区| 国产午夜福利在线观看视频| 一级毛片一级毛片一级毛片aaav | 精品香蕉伊思人在线观看| 在线免费观看毛片网站| 久久精品人人爽人人爽| 笨蛋英子未删1至925下载| 国产福利你懂的| 中国一级毛片在线观看| 欧美日韩一区二区三区自拍| 国产三级久久精品三级| 99国产精品久久久久久久成人热| 日韩福利电影在线观看| 俺来也俺去啦久久综合网| 国产精品久久自在自线观看| 好男人社区www在线官网|