亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數(shù)據(jù)庫 > SQLite > 正文

詳解SQLite中的查詢規(guī)劃器

2020-10-29 21:49:32
字體:
供稿:網(wǎng)友

 1.0 介紹

查詢規(guī)劃器的任務(wù)是找到最好的算法或者說“查詢計(jì)劃”來完成一條SQL語句。早在SQLite 3.8.0版本,查詢規(guī)劃器的組成部分已經(jīng)被重寫使它可以運(yùn)行更快并且生成更好的查詢計(jì)劃。這種重寫被稱作“下一代查詢規(guī)劃器”或者“NGQP”。

這篇文章重新概括了查詢規(guī)劃的重要性,提出來一些查詢規(guī)劃固有的問題,并且概括了NGQP是如何解決這些問題。

我們知道的是,NGQP(下一代查詢規(guī)劃器)幾乎總是比舊版本的查詢規(guī)劃器好。然而,也許有的應(yīng)用程序在舊版本的查詢規(guī)劃器中已經(jīng)不知不覺依賴了一些不確定或者不是很好的特性,這時(shí)候?qū)⒉樵円?guī)劃器更新升級(jí)到NGQP,這些應(yīng)用程序可能會(huì)導(dǎo)致程序閃退現(xiàn)象。NGQP必須考慮這種風(fēng)險(xiǎn),提供一系列的檢查項(xiàng)來減小風(fēng)險(xiǎn)和解決可能會(huì)引起的問題。

在NGOP上關(guān)注此文檔。對(duì)于更一般的sqlite查詢規(guī)劃器以及涵蓋sqlite整個(gè)歷史的概述,請(qǐng)參閱:“sqlite查詢優(yōu)化程序概述”。
2.0 背景

對(duì)于用簡(jiǎn)單的幾個(gè)指數(shù)對(duì)單個(gè)表的查詢,通常會(huì)有一個(gè)明顯的最佳的算法選擇。但是對(duì)于更大更復(fù)雜的查詢,諸如眾多指數(shù)與子查詢的多路連接,對(duì)于計(jì)算結(jié)果,可能有數(shù)百,數(shù)千或者數(shù)百萬的合理算法。如此查詢規(guī)劃的工作是選擇一個(gè)單一的“最好”的有眾多可能性的查詢計(jì)劃。

查詢規(guī)劃器是什么使得SQL數(shù)據(jù)庫引擎變得如此驚人的有用與強(qiáng)大。(這是真正的所有的sql數(shù)據(jù)庫引擎,不只是sqlite。)查詢規(guī)劃器使得編程人員從苦差事中選擇一個(gè)特定的查詢計(jì)劃之中釋放出來。從而允許程序員在更高級(jí)別的應(yīng)用問題里和向最終端用戶提供更多的價(jià)值之上可以關(guān)注更多的心理能量。對(duì)于簡(jiǎn)單的查詢,查詢計(jì)劃的選擇是顯而易見的,雖然是方便的,但并不是很重要的。但是作為應(yīng)用程序,架構(gòu)與查詢將會(huì)變得越來越復(fù)雜化。一個(gè)聰明的查詢規(guī)劃可以大大地加速和簡(jiǎn)化應(yīng)用程序開發(fā)的工作。它告訴數(shù)據(jù)庫引擎有什么內(nèi)容需求是有著驚人的力量的,然后,讓數(shù)據(jù)庫引擎找出最好的辦法檢索那些內(nèi)容。

寫一個(gè)好的查詢規(guī)劃器是藝術(shù)多于科學(xué)。查詢規(guī)劃器必須要有不完整的信息。它不能決定將多久采取任何特別的計(jì)劃,而實(shí)際上無需運(yùn)行此計(jì)劃。因此,當(dāng)比較兩個(gè)或多個(gè)計(jì)劃時(shí),找出哪些是“最好的”,查詢規(guī)劃器會(huì)做出一些假設(shè)和猜測(cè),那些假設(shè)和猜測(cè)有時(shí)候會(huì)出錯(cuò)。一個(gè)好的查詢計(jì)劃要求能找到正確的解決方案,而這些問題是程序員很少考慮的。
2.1 sqlite之中的查詢規(guī)劃器

sqlite的計(jì)算使用嵌套循環(huán)聯(lián)接,一個(gè)循環(huán)中每個(gè)標(biāo)的連接(額外的循環(huán)可能會(huì)在WHERE句子中插入IN和OR運(yùn)算符。sqlite認(rèn)為那些考慮太多啦,但為了簡(jiǎn)單起見,我們可以在這篇文章之中可以忽略它。)在每次循環(huán)時(shí),一個(gè)或者更多的指數(shù)被使用,并被加速搜索,或者一個(gè)循環(huán)可能是“全表掃描”讀取表中每一行。因此,查詢規(guī)劃分解成兩個(gè)子任務(wù):

  •     采摘的各種循環(huán)的嵌套順序。
  •     選擇每個(gè)循環(huán)的良好指數(shù)。

采摘嵌套順序一般是更具挑戰(zhàn)性地問題。

一旦建立連接的嵌套順序,每個(gè)循環(huán)指數(shù)的選擇通常是顯而易見的。

2.2 SQLite查詢規(guī)劃器穩(wěn)定性保證

對(duì)于給出的任何SQL語句,SQLite 通常情況下會(huì)選擇相同的查詢規(guī)劃假如:

  •     數(shù)據(jù)庫的schema沒有明顯的改變,例如添加或刪除索引(indices),
  •     ANALYZE命令沒有返回
  •     SQLite在編譯時(shí)沒有使用SQLITE_ENABLE_STAT3或者SQLITE_ENABLE_STAT4,并且
  •     使用相同版本的SQLite

SQLite的穩(wěn)定性保證意味著你在測(cè)試中高效的運(yùn)行查詢操作,并且你的應(yīng)用沒有更改schema,那么SQLite不會(huì)突然選擇開始使用一個(gè)不同的查詢規(guī)劃,那樣有可能在你把你的應(yīng)用發(fā)布給用戶之后造成性能問題。如果在實(shí)驗(yàn)室里你的應(yīng)用是工作的,那它在部署之后同樣可以工作。

企業(yè)級(jí)的客戶端/服務(wù)器SQL數(shù)據(jù)庫通常不能做這樣的保證。在客戶端/服務(wù)器SQL數(shù)據(jù)庫引擎里,服務(wù)器跟蹤統(tǒng)計(jì)表的大小和索引(indices)的數(shù)量,查詢規(guī)劃器根據(jù)這些統(tǒng)計(jì)信息選擇最優(yōu)的規(guī)劃。一旦在數(shù)據(jù)庫的內(nèi)容通過增刪改改變,統(tǒng)計(jì)信息的改變有可能引起對(duì)于某些特定的查詢,查詢規(guī)劃器使用不同的查詢規(guī)劃。通常新的規(guī)劃對(duì)于更改過的數(shù)據(jù)結(jié)構(gòu)來說更好。但有時(shí)新的查詢規(guī)劃會(huì)導(dǎo)致性能的下降。在使用客戶端/服務(wù)器數(shù)據(jù)庫引擎時(shí),通常會(huì)有一個(gè)數(shù)據(jù)庫管理員(DBA)來處理這些罕見的問題。但是DBA們不能在像SQLite這樣的嵌入式數(shù)據(jù)庫中修復(fù)該問題,所以SQLite需要小心的確保查詢規(guī)劃在部署之后不會(huì)被意外的改變。

SQLite穩(wěn)定性保證適用于傳統(tǒng)的查詢規(guī)劃和NGQP。

需要注意的很重要的一點(diǎn)是SQLite版本的改變可能引起查詢規(guī)劃的改變。同版本的SQLite通常會(huì)選擇相同的查詢規(guī)劃,但是如果你把你的應(yīng)用重新連接到了不同版本的SQLite上,那么查詢規(guī)劃可能會(huì)改變。在很罕見的情況下,SQLite版本的改變會(huì)引起性能衰減。這是一個(gè)你應(yīng)該考慮把你的應(yīng)用靜態(tài)的連接到SQLite而不是使用一個(gè)系統(tǒng)范圍(system-wide)的SQLite共享庫的原因,因?yàn)樗锌赡茉谀悴恢榛蛘卟荒芸刂频那闆r下改變。
3.0 一個(gè)棘手的情況

"TPC-H Q8"是一個(gè)來自于Transaction Processing Performance Council的測(cè)試查詢。查詢規(guī)劃器在3.7.17以及之前版本的SQLite中沒有為TPC-H Q8選擇一個(gè)好的規(guī)劃。并且被認(rèn)定再怎么調(diào)整傳統(tǒng)查詢規(guī)劃器也不能修復(fù)這個(gè)問題。為了給TPC-H Q8查詢尋找一個(gè)好的好的解決方案,并且能夠持續(xù)的改進(jìn)SQLite查詢規(guī)劃器的質(zhì)量,我們有必要重新設(shè)計(jì)查詢規(guī)劃器。這個(gè)部分將解釋為什么重新設(shè)計(jì)是有必要的,NGQP有什么不同和設(shè)法解決TPC-H Q8問題。

3.1 查詢細(xì)節(jié)

TPC-H Q8 是一個(gè)8路的join?;谝陨纤吹降模樵円?guī)劃器的主要任務(wù)是確定這八次循環(huán)最好的嵌套順序,從而將完成join操作的工作量最小化。下圖就是TPC-H Q8例子的簡(jiǎn)單模型:

201572144655521.gif (636×379)

 在這個(gè)圖表中,在查詢語句中的from從句部分的8個(gè)表都被表示成一個(gè)大的圓形,并用from從句的名字標(biāo)識(shí):N2, S, L, P, O, C, N1 和R。圖中的弧線代表計(jì)算圓弧起點(diǎn)的表格做外連接所對(duì)應(yīng)的預(yù)估開銷。舉個(gè)例子,S內(nèi)連接L的開銷是2.30,S外連接L的開銷是9.17。

這兒的“資源消耗”是通過對(duì)數(shù)運(yùn)算算出來的。由于循環(huán)是嵌套的,因此總的資源消耗是相乘得到的,而不是相加。通常都認(rèn)為圖帶的是要累加的權(quán)重,然而這兒的圖顯示的是各種資源消耗求對(duì)數(shù)后的值。上圖顯示S位于L內(nèi)部要少消耗大約6.87,轉(zhuǎn)換后就是S循環(huán)位于L循環(huán)內(nèi)部的查詢比S循環(huán)位于L循環(huán)外部的查詢要運(yùn)行快大約963倍。

從標(biāo)記為“*”的小圓圈開始的箭頭表示單獨(dú)運(yùn)行每個(gè)循環(huán)所消耗的資源。外循環(huán)一定消耗的是“*”所消耗資源。內(nèi)循環(huán)可以選擇消耗"*"所消耗的資源,或者選擇其余項(xiàng)中的一個(gè)為外部循環(huán)所消耗的資源,無論選擇哪個(gè)都是為了得到最低的資源消耗。你可以把“*”所消耗的資源看作是圖中其他節(jié)點(diǎn)中的任意一個(gè)到當(dāng)前節(jié)點(diǎn)的多個(gè)弧線的簡(jiǎn)寫表示。因此說這樣的圖是“完整的”,也就是說圖中的每一對(duì)節(jié)點(diǎn)間都有兩個(gè)方向的弧線(一些是非常明顯的,一些則是隱含的)。

尋找最佳查詢規(guī)劃的問題就等同于尋找圖中訪問每個(gè)節(jié)點(diǎn)僅僅一次的最小消耗路徑。

(附注:TPC-H Q8圖里的資源消耗的評(píng)估是由SQLite 3.7.16里的 查詢規(guī)劃器計(jì)算,并使用自然對(duì)數(shù)轉(zhuǎn)換得來的 。)

3.2 復(fù)雜性

上面所展示的查詢規(guī)劃器問題是簡(jiǎn)化版的。資源的消耗可以估計(jì)出來。我們只有實(shí)際運(yùn)行了循環(huán)之后才能知道運(yùn)行這個(gè)循環(huán)真正的資源消耗是多少 。SQLite是根據(jù)WHERE子句的約束和可以使用的索引來估計(jì)運(yùn)行循環(huán)的資源消耗的。這樣的估計(jì)通常都八九不離十,不過有時(shí)候估計(jì)的結(jié)果卻脫離現(xiàn)實(shí)。使用ANALYZE命令收集數(shù)據(jù)庫的其他統(tǒng)計(jì)信息有時(shí)候可以讓SQLite對(duì)消耗的資源的評(píng)估更準(zhǔn)確。

消耗的資源是由多個(gè)數(shù)字組成的,而不是像上圖一樣只是有一個(gè)單獨(dú)的數(shù)字組成。SQLite針對(duì)每個(gè)循環(huán)的不同階段計(jì)算出幾個(gè)不同的評(píng)估的消耗的資源。例如 ,“初始化”資源耗費(fèi)僅僅發(fā)生在查詢啟動(dòng)的哪個(gè)時(shí)候。初始化消耗的資源是對(duì)沒有索引的表進(jìn)行自動(dòng)索引所消耗的資源 。接著是運(yùn)行循環(huán)的每一步所消耗的資源。最后評(píng)估循環(huán)自動(dòng)生成的行數(shù),行數(shù)是評(píng)估內(nèi)循環(huán)所消耗資源所必需的信息。如果查詢含有ORDER BY子句,那么排序所消耗的資源也要考慮。

常用的查詢里的依賴并不一定在一個(gè)單獨(dú)的循環(huán)上,因此依賴的模型可能無法用圖來表示。例如,WHERE子句的約束之一可能是S.a=L.b+P.c,這就隱含地說S循環(huán)一定是L和P的內(nèi)循環(huán)。這樣的依賴不可能用圖來表示 ,因?yàn)闆]有辦法繪出同時(shí)從兩個(gè)或者兩個(gè)以上節(jié)點(diǎn)出發(fā)的一條弧線。

如果查詢包含有ORDER BY子句或者GROUP BY子句,或者查詢使用了DISTINCT關(guān)鍵字,那么就會(huì)自動(dòng)對(duì)行進(jìn)行排序,形成一個(gè)圖,選擇遍歷這個(gè)圖的路徑就顯得尤為便利,因此也不需要單獨(dú)進(jìn)行排序了。自動(dòng)刪除ORDER BY子句可以讓性能有巨大的變化,因此要完成規(guī)劃器的完整實(shí)現(xiàn),這也是一個(gè)需要考慮的因素。

在TPC-H Q8查詢里,所有的初始化資源消耗是微不足道的,各個(gè)節(jié)點(diǎn)之前都存在依賴,而且沒有ORDER BY,GROUP BY或者DISTINCT子句。因此,對(duì)TPC-H Q8來說,上圖足以表示計(jì)算資源消耗所需的東西。通常的查詢可能涉及到許多其他復(fù)雜的情形,為了能夠清晰的說明問題,這篇文章的后續(xù)部分就忽略了使問題復(fù)雜化的許多因素。

3.3 尋找最佳查詢規(guī)劃

在版本3.8.0之前,SQLite一直使用“最近鄰居” 或者“NN"試探法尋找最佳查詢規(guī)劃。NN試探法對(duì)圖進(jìn)行一次單獨(dú)的遍歷,總是選擇消耗最低的哪個(gè)弧線作為下一步。大多數(shù)情況下,NN試探法運(yùn)行的非常地好。而且,NN試探法也很快,因此SQLite即便是達(dá)到64個(gè)連接的情況下也能夠快速的找到很好的規(guī)劃。與此相反,可以運(yùn)行更大量搜索的其他數(shù)據(jù)庫引擎在同一連接中表的數(shù)目大于10或者15時(shí)就會(huì)停止不動(dòng)。

很不幸,NN試探法對(duì)TPC-H Q8所計(jì)算出的查詢規(guī)劃不是最佳的。由NN試探法計(jì)算出的規(guī)劃是R-N1-N2-S-C-O-L-P,其資源消耗是36.92。前一句的意思是: R表運(yùn)行在最外層循環(huán),N1是位于緊接著的內(nèi)部循環(huán),N2是位于第三個(gè)循環(huán),以此類推到P,它位于最內(nèi)層的循環(huán)。遍歷此圖的(由窮舉搜索可得到的)最短路徑是P-L-O-C-N1-R-S-N2,此時(shí)的資源耗費(fèi)是27.38。差異看起來似乎并不大,不過,要記得消耗的資源是經(jīng)過對(duì)數(shù)運(yùn)算計(jì)算出來的,因此最短路徑比由NN試探法得出的路徑快幾乎750倍。

這個(gè)問題的一個(gè)解決方法就是更改SQLite,讓它使用窮舉搜索獲取最佳路徑。然而,窮舉搜索所需要的時(shí)間與K成正比?。↘是連接涉及的表數(shù)目),因此當(dāng)有10個(gè)以上的連接的時(shí)候,運(yùn)行sqlite3_prepare()所耗費(fèi)的時(shí)間丟非常大。
3.4 N個(gè)最近鄰居試探法或者"N3"試探法

下一代查詢規(guī)劃器使用新的試探法查找遍歷圖的最佳路徑:"N個(gè)最近鄰居試探法"(后面就叫"N3")。用N3的話,每一步就不是僅僅選擇一個(gè)最近鄰居,N3算法在每一步要跟蹤N個(gè)最佳路徑,這兒N是個(gè)小整數(shù)。

假設(shè)N=4,那么對(duì)TPC-H Q8圖來說 ,第一步找到可訪問任何單個(gè)節(jié)點(diǎn)的四個(gè)最短路徑:
 R (cost: 3.56)
N1 (cost: 5.52)
N2 (cost: 5.52)
P (cost: 7.71)

第二步找到以前一步找到的四個(gè)最短路徑之一開始的可訪問兩個(gè)節(jié)點(diǎn)的四個(gè)最短路徑。這種情況下,兩個(gè)或者兩個(gè)以上的路徑是可以的(這樣的路徑具有相同的可訪問的節(jié)點(diǎn)集,可能順序不同),只要記住是必須保持第一步的路徑和最低資源消耗路徑就可以。我們找到以下路徑:
 
R-N1 (cost: 7.03)
R-N2 (cost: 9.08)
N2-N1 (cost: 11.04)
R-P {cost: 11.27}

第三步以可訪問兩個(gè)節(jié)點(diǎn)四個(gè)最短路徑為起點(diǎn),找到可訪問三個(gè)節(jié)點(diǎn)的四個(gè)最短路徑:
 
R-N1-N2 (cost: 12.55)
R-N1-C (cost: 13.43)
R-N1-P (cost: 14.74)
R-N2-S (cost: 15.08)

以此類推。TPC-H Q8查詢有8個(gè)節(jié)點(diǎn),因此如此的過程總共重復(fù)8次。在通常K個(gè)連接的情況下,存儲(chǔ)需求復(fù)雜度是O(N),計(jì)算的時(shí)間復(fù)雜度是O(K*N),它明顯比O(2 K)方案快多了。

然而,N選擇哪個(gè)值呢?你可以試試N=K,此時(shí)這種算法的復(fù)雜度是O(K2) ,實(shí)際上仍然非常相當(dāng)快,由于K的最大值為64,而且K很少超過10。不過這仍然不足以解決TPC-H Q8問題。如果TPC-H Q8查詢進(jìn)行時(shí)N=8,此時(shí)N3算法得到的查詢規(guī)劃是R-N1-C-O-L-S-N2-P,此時(shí)資源耗費(fèi)是29.78。這對(duì)NN算法進(jìn)行了很大的改進(jìn),不過仍然不是最佳的。當(dāng)N=10或者更大時(shí),N3才能找到TPC-H Q8查詢的最佳查詢規(guī)劃。

下一代查詢規(guī)劃的最初實(shí)現(xiàn)對(duì)簡(jiǎn)單查詢選N=1,兩個(gè)連接就選N=5,三個(gè)或者更多表連接選擇N=10。后續(xù)的發(fā)布版可能要更改N值得選擇規(guī)則。

4.0 升級(jí)到下一代查詢規(guī)劃器的風(fēng)險(xiǎn)

對(duì)大多數(shù)應(yīng)用來說,從舊查詢規(guī)劃器升級(jí)到下一代查詢規(guī)劃器不需要多想,或者不需要花很多功夫就可以做到。只要簡(jiǎn)單地把舊的SQLite版本換成較新的SQLite版本,然后重新編譯就行,此時(shí)運(yùn)行應(yīng)用就會(huì)快很多。不需要對(duì)復(fù)雜過程的API進(jìn)行更改或者修正。

然而,像其他查詢器更換一樣,升級(jí)到下一代查詢規(guī)劃器確實(shí)可以引起性能下降這樣的小風(fēng)險(xiǎn)。出現(xiàn)這種問題不是因?yàn)橄乱淮樵円?guī)劃器不正確、或者說漏洞多,或者說比舊的查詢規(guī)劃器差。假若選擇索引的信息確定,那么下一代查詢規(guī)劃器總能選擇一個(gè)比以前好的或者說更優(yōu)秀的規(guī)劃。存在的問題是某些應(yīng)用也許使用了低質(zhì)量的、沒有多少選擇性的索引,而且無法運(yùn)行ANALYZE。舊的查詢規(guī)劃器對(duì)每個(gè)查詢都查看了許多但比現(xiàn)在要少的可能的查詢實(shí)現(xiàn),因此如果運(yùn)氣好的話,這樣的規(guī)劃器可能就碰到好的規(guī)劃。而另一方面,下一代查詢規(guī)劃器查看了更多地查詢規(guī)劃實(shí)現(xiàn),所以理論上來講,它可能選擇另一個(gè)性能更好的查詢規(guī)劃,如果此時(shí)索引運(yùn)行良好,而實(shí)際運(yùn)行中性能卻有所下降,那么可能是數(shù)據(jù)的分布引起的.

技術(shù)要點(diǎn):

  •     只要下一代查詢規(guī)劃器可以訪問SQLITE STAT1文件里準(zhǔn)確的ANALYZE數(shù)據(jù),那么它總是能找到與以前查詢規(guī)劃器等同的或者性能更好的查詢規(guī)劃。
  •     只要查詢模式不包含最左邊字段具有相同值且有超過10或者20行的索引,那么下一代查詢規(guī)劃器總是能找到一個(gè)好的查詢規(guī)劃。

并不是所有的應(yīng)用都滿足上面條件。幸運(yùn)的是,即便不滿足這些條件,下一代查詢規(guī)劃器通常仍然能找到好的查詢規(guī)劃。不過,性能下降這種情況也有可能出現(xiàn)(不過很少)。

4.1范例分析:升級(jí)Fossil使用下一代查詢規(guī)器

Fossil DVCS是用來追蹤全部SQLite源代碼的版本控制系統(tǒng)。Fossil軟件倉庫就是一個(gè)SQLite數(shù)據(jù)庫文件。(作為單獨(dú)的練習(xí),我們邀請(qǐng)讀者對(duì)這種遞歸式的應(yīng)用查詢規(guī)劃器進(jìn)行深入思考。)Fossil既是SQLite的版本控制系統(tǒng),也是SQLite的測(cè)試平臺(tái)。無論什么時(shí)候?qū)QLite進(jìn)行強(qiáng)化,F(xiàn)ossil都是對(duì)這些強(qiáng)化進(jìn)行測(cè)試和評(píng)估的首批應(yīng)用之一。所以Fossil最早采用下一代查詢規(guī)劃器。

很不幸,下一代查詢規(guī)劃器引起了Fossil性能下降。

Fossil用到許多報(bào)表,其中之一就是單個(gè)分支上所做更改的時(shí)間表,它顯示了這個(gè)分支的所有合并和刪除。查看 http://www.sqlite.org/src/timeline?nd&n=200&r=trunk就可以看到時(shí)間報(bào)表的典型例子。通常生成這樣的報(bào)表只需要幾毫秒。然而,升級(jí)到下一代查詢規(guī)劃器之后,我們發(fā)現(xiàn)對(duì)倉庫的主干分支生成這樣的報(bào)表竟然需要近10秒的時(shí)間。


用來生成分支時(shí)間表的核心查詢?nèi)缦?。(我們不期望讀者理解這個(gè)查詢的細(xì)節(jié)。下面將給出說明。)
 

SELECT   blob.rid AS blobRid,   uuid AS uuid,   datetime(event.mtime,'localtime') AS timestamp,   coalesce(ecomment, comment) AS comment,   coalesce(euser, user) AS user,   blob.rid IN leaf AS leaf,   bgcolor AS bgColor,   event.type AS eventType,   (SELECT group_concat(substr(tagname,5), ', ')    FROM tag, tagxref    WHERE tagname GLOB 'sym-*'     AND tag.tagid=tagxref.tagid     AND tagxref.rid=blob.rid     AND tagxref.tagtype>0) AS tags,   tagid AS tagid,   brief AS brief,   event.mtime AS mtime FROM event CROSS JOIN blob WHERE blob.rid=event.objid  AND (EXISTS(SELECT 1 FROM tagxref        WHERE tagid=11 AND tagtype>0 AND rid=blob.rid)    OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid          WHERE tagid=11 AND tagtype>0 AND pid=blob.rid)    OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid          WHERE tagid=11 AND tagtype>0 AND cid=blob.rid)) ORDER BY event.mtime DESC LIMIT 200;

這個(gè)查詢不是特別復(fù)雜,不過,即便這樣,它仍然可以替代上百行,也許是上千行處理過程代碼。這個(gè)查詢的要點(diǎn)是:向下掃描EVENT表,查找滿足下列三個(gè)條件中任何一個(gè)的最新的200條提交記錄:

  1.     此提交含有"trunk"標(biāo)簽。
  2.     此提交有個(gè)子提交含有“trunk"標(biāo)簽。
  3.     此提交有個(gè)父提交含有“trunk"標(biāo)簽。

第一個(gè)條件將顯示所有主干分支上的提交,第二個(gè)和第三個(gè)條件包含合并到主干分支,或者由主干分支產(chǎn)生的提交。這三個(gè)條件是通過在此查詢的WHERE子句中用OR連接三個(gè)EXISTS語句實(shí)現(xiàn)的。使用下一代查詢規(guī)劃器引起的性能下降是由第二個(gè)和第三個(gè)條件產(chǎn)生的。兩個(gè)條件里存在的問題是相同的,因此我們只看第二個(gè)條件。第二個(gè)條件的子查詢可以重寫為如下語句(把次要的和不重要的進(jìn)行了簡(jiǎn)化):
 

SELECT 1 FROM plink JOIN tagxref ON tagxref.rid=plink.cid WHERE tagxref.tagid=$trunk  AND plink.pid=$ckid;

PLINK表保存著各個(gè)提交之間的父子關(guān)系。TAGXREF表把標(biāo)簽映射到提交上。作為參考,對(duì)這兩個(gè)表進(jìn)行查詢的模式的相關(guān)部分顯示如下:
 

CREATE TABLE plink( pid INTEGER REFERENCES blob, cid INTEGER REFERENCES blob);CREATE UNIQUE INDEX plink_i1 ON plink(pid,cid); CREATE TABLE tagxref( tagid INTEGER REFERENCES tag, mtime TIMESTAMP, rid INTEGER REFERENCE blob, UNIQUE(rid, tagid));CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);


實(shí)現(xiàn)這樣的查詢只有兩個(gè)方法值得考慮。(當(dāng)然可能還有許多其他算法,不過它們中的任何一個(gè)都不是“最佳”算法的競(jìng)爭(zhēng)者。

  •     查找提交$ckid的所有子提交,然后對(duì)每一個(gè)進(jìn)行測(cè)試,看看是否有子提交包含$trunk標(biāo)簽
  •     查找所有包含$trunk標(biāo)簽的提交,然后對(duì)每個(gè)這樣的提交進(jìn)行測(cè)試,看看是否有$ckid提交的子提交。

僅憑直覺,我們?nèi)祟愓J(rèn)為第一個(gè)算法是最佳選擇。每個(gè)提交可能有幾個(gè)子提交(其中有一個(gè)提交是我們最常用到的。),然后對(duì)每個(gè)子提交進(jìn)行測(cè)試,用對(duì)數(shù)運(yùn)算計(jì)算出查找到$trunk標(biāo)簽的時(shí)間。實(shí)際上,算法1確實(shí)較快。然而下一代查詢規(guī)劃器卻沒有使用人們直覺上的最佳選擇。下一代查詢規(guī)劃器一定是選擇了很難得算法,算法2在數(shù)學(xué)上相對(duì)稍微難些。這是因?yàn)椋涸跊]有其他信息的情況下下一代查詢規(guī)劃器一定假設(shè)PLINK_I1和TAGXREF_I1索引具有同等的質(zhì)量和同等的可選擇性。算法2使用了TAGXREF_I1索引的一個(gè)字段,PLINK_I1索引的兩個(gè)字段,而算法1只是使用了這兩個(gè)索引的第一個(gè)字段。正是由于算法2使用了多個(gè)字段的索引,所以下一代查詢規(guī)劃器才會(huì)以自己的標(biāo)準(zhǔn)正確地確定它作為兩種算法中性能較好的算法。兩個(gè)算法所花費(fèi)的時(shí)間非常接近,算法2 只是勉強(qiáng)稍稍領(lǐng)先算法1。不過,這種情況下,選擇算法2確實(shí)是正確的。


很不幸,在實(shí)際的應(yīng)用中算法2比算法1要慢些。

出現(xiàn)這樣的問題是因?yàn)樗饕⒉皇蔷哂型荣|(zhì)量。一個(gè)提交有可能只有一個(gè)子提交。這樣PLINK_I1索引的第一個(gè)字段通??s減值對(duì)一行進(jìn)行搜索。不過由于成千上萬的提交都包含有"trunk"標(biāo)簽,所以TAGXREF_I1的第一個(gè)字段對(duì)縮減搜索不會(huì)有多大幫助。

下一代查詢規(guī)劃器是沒有辦法知道TAGXREF_I1在這樣的查詢中幾乎沒有什么用處,除非在數(shù)據(jù)庫上運(yùn)行ANALYZE。ANALYZE命令 收集了各個(gè)索引的質(zhì)量統(tǒng)計(jì)信息,并把 這些統(tǒng)計(jì)信息存儲(chǔ)到SQLITE_STAT1表里。如果下一代查詢規(guī)劃器能夠訪問這些統(tǒng)計(jì)信息 ,那么在很大程度上它就會(huì)非常容易地選擇算法1作為最佳算法。


難道舊查詢規(guī)劃器沒有選擇算法2?很簡(jiǎn)單:因?yàn)镹N算法甚至從來都沒有考慮到算法2。這類規(guī)劃問題的圖示如下:

201572144725493.gif (537×233)

在如左圖那樣“沒有運(yùn)行ANALYZE“的情況下,NN算法選擇循環(huán)P9PLINK)作為外循環(huán),因?yàn)?.9比5.2要小,結(jié)果就是選擇P-T路徑,即算法1。NN算法只是在每一步查找一個(gè)最佳選擇路徑,因此它完全忽略了這樣一個(gè)事實(shí):5.2+4.4是比4.9+4.8性能稍稍有些好的規(guī)劃。然而N3算法對(duì)著兩個(gè)連接追蹤了5個(gè)最佳路徑,因此它最終選擇了T-P路徑,因?yàn)檫@條路徑的總體資源消耗要少一些。路徑T-P就是算法2。

注意: 如果運(yùn)行了ANALYZE,那么對(duì)資源消耗的評(píng)估就更加接近于現(xiàn)實(shí),這樣NN和N3都選擇算法1。

(附注:最新的兩圖中對(duì)資源消耗的評(píng)估是下一代查詢規(guī)劃器使用以2為底的對(duì)數(shù)算法計(jì)算得出來的,而且與舊查詢規(guī)劃器相比假設(shè)的資源消耗稍微有些不同。因此,最后兩個(gè)圖中的資源消耗評(píng)估不能與TPC-H Q8圖里的資源消耗評(píng)估進(jìn)行比較。)

4.2 問題修正

對(duì)資源倉庫數(shù)據(jù)庫運(yùn)行ANALYZE可立即修復(fù)這類性能問題。然而,無論是否對(duì)資源倉庫是否進(jìn)行分析,我們都要求Fossil十分強(qiáng)壯,而且總是能夠快速地運(yùn)行?;谶@個(gè)原因,我們修改查詢使用CROSS JOIN操作符而不使用常用的JOIN操作符。SQLite將不會(huì)對(duì)CROSS JOIN連接的表重新排序。這個(gè)功能是SQLite中長(zhǎng)期都有的一個(gè)功能,做這么特別的設(shè)計(jì)就是允許具有豐富經(jīng)驗(yàn)的開發(fā)人員能夠強(qiáng)制SQLite執(zhí)行特定的嵌套循環(huán)順序。一旦某個(gè)連接更改為(增加了一個(gè)關(guān)鍵字的)CROSS JOIN這樣的連接,下一代查詢規(guī)劃器就不管是否使用ANALYZE收集統(tǒng)計(jì)統(tǒng)計(jì)信息都強(qiáng)制選擇稍稍快一點(diǎn)的算法1。

我們說算法1"快一些“,不過,嚴(yán)格來說這么說不準(zhǔn)確。對(duì)一個(gè)常見的存儲(chǔ)倉庫運(yùn)行算法1是快一些,不過,可能構(gòu)建這樣一種資源倉庫:對(duì)資源倉庫的每一次提交都是提交給不同的名字唯一的分支上,而且所有的提交都是根提交的子提交。這種情況下,TAGXREF_I1與PLINK_I1相比就具有更多的可選項(xiàng)了,此時(shí)算法2才真正快一些。然而實(shí)際中這樣的資源倉庫極不可能出現(xiàn),所以使用CROSS JOIN語法硬編碼嵌套循環(huán)的順序是解決這種情形下存在問題的適合方案。

5.0 避免或者修正查詢規(guī)劃器問題的方法一覽表

    不要驚慌!查詢規(guī)劃器選擇差的規(guī)劃這種情況實(shí)際上是非常罕見的。你未必會(huì)在應(yīng)用中碰到這樣的問題。如果你沒有性能方面問題,那么你就不必為此而擔(dān)心。

    創(chuàng)建正確的索引。大多數(shù)SQL性能問題不是因?yàn)椴樵円?guī)劃器問題而引起的,而是因?yàn)槿鄙俸线m的索引。確保索引可以促進(jìn)所有大型的查詢。大多數(shù)性能問題都可以使用一個(gè)或者兩個(gè)CREATE INDEX命令來解決,而不需要對(duì)應(yīng)用代碼進(jìn)行修改。

    避免創(chuàng)建低質(zhì)量的索引。(用于解決查詢規(guī)劃器問題而創(chuàng)建的)低質(zhì)量索引是這樣的索引:表里的索引最左一個(gè)字段具有相同值的行超過10行或者20行。特別注意,避免使用布爾字段或或者“枚舉類型”字段作為索引的最左一字段。

    這篇文章的前一段所說的Fossil性能問題是因?yàn)門AGXREF表的TAGXREF_I1索引的最左一子段(TAGID字段)具有相同值得項(xiàng)超過1萬。

    如果你一定要使用低質(zhì)量的索引,那么請(qǐng)一定要運(yùn)行ANALYZE。只要查詢規(guī)劃器知道那個(gè)索引時(shí)低質(zhì)量的,那么低質(zhì)量的索引就不會(huì)讓它迷惑。查詢規(guī)劃器知曉低質(zhì)量索引的方法是通過SQLITE_STAT1表的內(nèi)容來實(shí)現(xiàn)的,這個(gè)表示有ANALYZE命令計(jì)算得來的。

    當(dāng)然,ANALYZE只有在數(shù)據(jù)庫一開始就擁有非常大量的內(nèi)容的情況下才能夠高效地運(yùn)行。當(dāng)你希望創(chuàng)建一個(gè)數(shù)據(jù)庫并累積了大量數(shù)據(jù)的時(shí)候,你可以運(yùn)行命令"ANALYZE sqlite_master"創(chuàng)建SQLITE_STAT1表,然后(使用常用的INSERT語句)向SQLITE_STAT1表中填入用來說明這樣的數(shù)據(jù)庫正適合你的應(yīng)用的內(nèi)容-也許這樣的內(nèi)容是你在對(duì)實(shí)驗(yàn)室的某個(gè)填寫的非常完美的模板數(shù)據(jù)庫運(yùn)行ANALYZE命令后所獲得的。

    編寫你自己的代碼。增加可以讓你快速且非常容易就能知道哪些查詢需要很多時(shí)間,這樣就只運(yùn)行哪些特別不需要花太長(zhǎng)時(shí)間的查詢。

    如果查詢可能使用沒有運(yùn)行分析的數(shù)據(jù)庫上的低質(zhì)量索引,那么請(qǐng)使用CROSS JOIN語法,強(qiáng)制使用特定的嵌套循環(huán)順序。SQLite對(duì)CROSS JOIN操作符進(jìn)行特殊的處理,它強(qiáng)制左表為右表的外部循環(huán)。

    如果有其他方法實(shí)現(xiàn),那么就避免這么做,因?yàn)樗c任何一個(gè)SQL語言理念里的強(qiáng)大的優(yōu)點(diǎn)相抵觸,特別是應(yīng)用開發(fā)人不需要了解查詢規(guī)劃。如果你使用了CROSS JOIN,那么直到開發(fā)周期的后期你也要這么做,而且要在注釋里仔細(xì)地說明CROSS JOIN是如何使用的,這樣以后才有可能把它去掉。在開發(fā)周期的早期就避免使用CROSS JOIN,因?yàn)檫@么做是不成熟的優(yōu)化措施,也就是眾所周知的萬惡之源。

    使用單目運(yùn)算符"+",取消WHERE子句某些限制條件。當(dāng)對(duì)某個(gè)具體的查詢有更高質(zhì)量的索引可以使用的時(shí)候,如果查詢規(guī)劃器仍然堅(jiān)持選擇差質(zhì)量的索引,那么請(qǐng)?jiān)赪HERE子句中謹(jǐn)慎地使用單目運(yùn)算符"+",這樣做就可以強(qiáng)制查詢規(guī)劃器不使用差質(zhì)量的索引。如果可能的話,就盡量小心地添加這個(gè)這個(gè)運(yùn)算符,而且尤其避免在應(yīng)用開發(fā)的周期的早期就使用。特別要注意:給一個(gè)與類型密切相關(guān)的等號(hào)表達(dá)式增加單目運(yùn)算符"+"可能更改這個(gè)表達(dá)式的結(jié)果。

    使用INDEXED BY語法,強(qiáng)制有問題的查詢選擇特定的索引。同前兩個(gè)標(biāo)題一樣,如果可能的話,盡量避免使用這個(gè)方法,尤其避免在開發(fā)的早期這么做,因?yàn)楹芮宄?,它是一個(gè)不成熟的優(yōu)化措施。


6.0 結(jié)論

SQLite的查詢規(guī)劃器做這樣的工作做得非常好:為正在運(yùn)行的SQL語句選擇快速算法。對(duì)舊查詢規(guī)劃器來說,這是事實(shí),對(duì)新的下一代查詢規(guī)劃器來說更是這樣。也許偶然會(huì)出現(xiàn)這樣的情況:由于信息不完整,查詢規(guī)劃器選擇了稍差的查詢規(guī)劃。 與使用舊查詢規(guī)劃器相比,使用下一代查詢規(guī)劃器這種情形就會(huì)更少出現(xiàn)了,不過仍然有可能出現(xiàn)。即便出現(xiàn)了這種極少出現(xiàn)的情況,應(yīng)用開發(fā)人員需要做的是了解和幫助查詢規(guī)劃器做正確的事情。通常情況下,下一代查詢規(guī)劃器只是對(duì)SQLite做了一個(gè)新的增強(qiáng),這種增強(qiáng)可以讓應(yīng)用運(yùn)行的更快些,而且不需要開發(fā)人員做更多的思考或者動(dòng)作。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
亚洲1024| 独立日3在线观看完整版| 日本超碰在线观看| 日韩激情中文字幕| 精品人妻一区二区三区香蕉| 中文av一区| 欧美自拍丝袜亚洲| 国产 日韩 亚洲 欧美| 国产美女在线播放| 亚洲成av人片在线观看无| www浪潮av99com| www.狠狠色.com| 国产精品一区二区av白丝下载| 无遮挡爽大片在线观看视频| 国产综合色在线| 在线看日韩av| 成人丝袜18视频在线观看| 中文字幕一区二区在线观看视频| 国产九九在线观看| 国产精品色哟哟| 特黄特色大片免费视频大全| 色婷婷av一区二区| 人妻无码一区二区三区久久99| 里番在线观看网站| 波多野结衣高清在线| 日韩av一二区| 国产麻豆精品一区二区三区v视界| 黄网视频在线观看| 极品国产91在线网站| 欧美理论电影| 色94色欧美sute亚洲线路一久| 99久久精品国产一区| 91亚洲国产成人久久精品| 天堂色在线视频| 亚洲美女视频| 国精产品一品二品国精品69xx| 午夜男人视频在线观看| 在线免费福利| 欧美精品激情在线| 精品国产91九色蝌蚪| 新版中文在线官网| 欧美性做爰猛烈叫床潮| 奇米四色中文综合久久| 国产乱人视频免费播放| 国产区精品在线观看| 先锋影音av资源网| 九色在线视频蝌蚪| 亚洲欧美国产精品| 青青草视频在线青草免费观看| 欧美一区三区四区| 欧美日韩一区二区三区在线观看免| 精品国产无码在线| 黄色片子免费| 日韩欧美一级在线| 色女人综合av| 亚洲欧美www| 国产精品不卡一区二区三区| 欧美激情论坛| 99t1这里只有精品| 人妻人人澡人人添人人爽| 369你懂的电影天堂| 国产日韩亚洲欧美综合| 欧美男男青年gay1069videost| 五月天综合在线| 国产成人精品一区二区无码呦| 亚洲九九九在线观看| 欧美在线观看一二区| 亚洲成人激情综合网| 毛片毛片毛片| 91久久精品一区| 国产理论视频在线观看| 国产区视频在线| 一路向西2在线观看| 日本黄网站色大片免费观看| 老司机福利在线视频| 欧美一区二区三区视频免费播放| 天天综合一区| 久久成人综合| 午夜成人免费电影| 日本亚洲视频在线| www污污网站在线看| 亚洲午夜精品在线| 小早川怜子久久精品中文字幕| 黄色精品在线看| 亚洲成精国产精品女| 国产精品热视频| 小视频福利在线| 一区二区视频网站| 尤物在线观看| 777久久久精品一区二区三区| 国产在线不卡视频| 成人一区二区视频| 国产成人鲁色资源国产91色综| 欧美自拍视频在线| 欧美日韩精品在线观看视频| 精品国产一二| 亚洲第一网站在线观看| 人成免费在线视频| 亚洲男人的天堂网站| 999国内精品视频在线| 免费人成黄页网站在线一区二区| 国产又粗又大又长| 亚欧精品一区| 国产精品一区在线观看你懂的| 亚洲大全视频| 国产美女激情视频| 污视频网站免费在线观看| 亚洲精品亚洲人成人网| 在线天堂av| 国产日韩视频一区二区三区| 日本污视频在线观看| 精品国产髙清在线看国产毛片| 成人一区二区三区四区| 开心久久婷婷综合中文字幕| 日韩高清dvd碟片| 在线观看福利一区| 美女免费久久| 久久综合网色—综合色88| 一本久久a久久免费精品不卡| 国产成人精品视| 天天射天天干天天| 蜜桃麻豆91| 亚洲高清成人影院| 国产精品99999| 精品国产欧美日韩| 欧美做受喷浆在线观看| 日韩久久视频| 国内精品久久久久久野外| 在线亚洲人成电影网站色www| 日本国产欧美| 日本视频在线| 免费福利视频一区二区三区| 色婷婷综合在线观看| 亚洲激情男女视频| 欧美精品乱人伦久久久久久| 欧美一区二区三区在线播放| 中文字幕一区二区三区四区在线视频| 国产一区二区三区在线观看网站| 一本大道av伊人久久综合| 日韩精品资源二区在线| 婷婷一区二区三区| 激情中文字幕| 色噜噜在线网| 久久riav| 天天插天天射| 免费在线观看一级毛片| 日本少妇裸体做爰| 国产精品天天摸av网| 日韩理论片在线| 成人18视频在线观看| 成人春色在线观看免费网站| 欧美视频三区| 国产精品成人久久| 久久久亚洲一区| 忘忧草精品久久久久久久高清| 中文日韩欧美| 香港三日本8a三级少妇三级99| 东凛在线观看| 成人性生交大片免费看视频直播| 亚洲福利专区| 猫咪成人在线观看| 欧美日本三区| 国产成人精品a视频一区www| 精品国产乱码久久久久久蜜柚| 日本天堂在线播放| 拍真实国产伦偷精品| 国产+高潮+白浆+无码| 麻豆传媒mv| 国产精品免费观看久久| 欧美牲交a欧美牲交aⅴ免费下载| 欧美性猛交xxxx免费看久久| 51免费午夜啪啪| 国产成人精品一区二区三区免费| 欧美另类激情| 欧美一区二区免费观在线| 午夜xxxxx| 91精品国产乱码久久久久久蜜臀| 自拍视频在线播放| 91亚洲精品久久久蜜桃| av色图一区| 成人毛片在线免费观看| 伊人中文字幕在线观看| 日韩一区二区在线视频| 神马午夜dy888| 色婷婷狠狠18禁久久| 日韩欧美亚洲国产另类| 国产资源在线免费观看| 91成人入口| 欧美日韩在线不卡| 日韩一级免费毛片| 欧美性大战久久久久| 色的视频在线免费看| 国产高潮国产高潮久久久91| 日日夜夜精品视频免费观看| 国产福利视频一区二区三区| 波多野结衣精品久久| 91黄色在线视频| 黄色免费视频网站| 激情久久av一区av二区av三区| 一级黄色录像免费看| 91精品国产91久久久久游泳池| 国产一区二区高清在线| 人妻一区二区三区免费| 女优一区二区三区| 欧美一区深夜视频| 亚洲女人毛片| 9191在线视频| 国产一级久久久久毛片精品| 日本不卡在线观看视频| 91精品91| 国产精品三级av在线播放| 天堂资源在线播放| 亚洲综合色激情五月| 日韩av一二三区| 91玉足脚交白嫩脚丫在线播放| ww国产内射精品后入国产| 五月天婷婷亚洲| 2019精品视频| 超免费在线视频| 亚洲综合精品国产一区二区三区| 免费日韩av电影| 日韩三级毛片| www.青青草.com| a视频在线看| 在线观看成年人视频| 在线观看日韩av电影| 亚洲国产精品自拍视频| 尤物在线精品| 国产蜜臀av在线播放| 成人高清免费在线播放| 激情在线小视频| 亚洲精品一二三区| 亚洲乱码电影| 亚洲欧美成人| 老司机午夜免费精品视频| 欧美另类第一页| 国产精品理伦片| frxxee中国xxx麻豆hd| 日本久久久精品视频| 免费精品国产| 免费cad大片在线观看| 日本xxx免费| 色无极亚洲影院| 九九久久国产精品| 日韩毛片一区二区三区| 亚洲欧洲精品一区二区三区波多野1战4| 最近中文字幕mv免费高清电影| www.三区| 亚洲午夜在线播放| 国产日产精品一区二区三区四区的观看方式| 亚洲欧美日韩中文视频| 精品人人视频| www黄色日本| 高清福利在线观看| 99色这里只有精品| 国家队第一季免费高清在线观看| 成人欧美一区二区三区黑人麻豆| 老师让我她我爽了好久视频| 成人黄色在线电影| 久久av免费看| 久久久噜噜噜久久狠狠50岁| 亚洲免费观看高清完整版在线| julia中文字幕久久亚洲蜜臀| 性欧美18一19sex性欧美| 精品视频在线视频| 在线观看av中文| 欧美精品欧美精品系列c| 在线观看wwww| 亚洲电影免费观看高清完整版在线观看| 天美传媒免费在线观看| 国产又爽又黄免费软件| 日韩一区二区三区免费视频| 日本 片 成人 在线| 精品福利在线导航| 992tv成人免费影院| 欧美色欧美亚洲另类二区精品| 亚洲丝袜另类动漫二区| 九色porny91| 国产精品不卡av| 国产真实生活伦对白| 91精品美女| 色偷偷亚洲第一成人综合网址| 久久草视频在线看| 一区二区免费在线观看视频| 国产网址在线观看| 五月天激情小说综合| 免费av网页| 在线观看网站免费入口在线观看国内| jizzjizzjizzjizz日本老师| 国产精品免费一区二区三区四区| 国产一级在线观看www色| 1769国产精品| 亚洲精品爱爱久久| 九九热在线免费观看| 99视频精品全国免费| 九九九九精品九九九九| 91成人国产在线观看| 国产伦精品一区二区三区妓女下载| 国产大学生自拍视频| 欧美三级午夜理伦| 亚洲av成人无码一二三在线观看| 亚洲午夜久久久| 久久激情视频久久| 欧美a大片欧美片| 精品一区二区三区免费视频| 亚洲丁香婷深爱综合| 久久99精品久久久久久青青日本| 在线影音av| 九九热爱视频精品视频高清| 久久亚洲影视婷婷| 色噜噜狠狠一区二区三区狼国成人| 香蕉视频成人在线| 欧洲久久久久久| 日本高清黄色片| 国产精品免费视频二三区| 欧美四级电影在线观看| 99久久er热在这里只有精品15| 国产中文字幕二区| 久久久久久久久久久久久女国产乱| 精品成人在线观看| 自拍偷拍亚洲视频| 久久99精品国产.久久久久久| 亚洲开心激情| 五月婷婷丁香在线| 欧美码中文字幕在线| 91在线看www| 日韩精品在线播放| 老太脱裤让老头玩ⅹxxxx|