国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

DBA整理的萬(wàn)字詳解mysql性能優(yōu)化,值得收藏!

共 15768字,需瀏覽 32分鐘

 ·

2021-06-24 12:30

     原文|t.hk.uy/wxP



引言


說(shuō)起MySQL的查詢優(yōu)化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *、不使用NULL字段、合理創(chuàng)建索引、為字段選擇合適的數(shù)據(jù)類型..... 你是否真的理解這些優(yōu)化技巧?是否理解其背后的工作原理?在實(shí)際場(chǎng)景下性能真有提升嗎?我想未必。因而理解這些優(yōu)化建議背后的原理就尤為重要,希望本文能讓你重新審視這些優(yōu)化建議,并在實(shí)際業(yè)務(wù)場(chǎng)景下合理的運(yùn)用。


MySQL邏輯架構(gòu)


如果能在頭腦中構(gòu)建一幅MySQL各組件之間如何協(xié)同工作的架構(gòu)圖,有助于深入理解MySQL服務(wù)器。下圖展示了MySQL的邏輯架構(gòu)圖。


MySQL邏輯架構(gòu),來(lái)自:高性能MySQL


MySQL邏輯架構(gòu)整體分為三層,最上層為客戶端層,并非MySQL所獨(dú)有,諸如:連接處理、授權(quán)認(rèn)證、安全等功能均在這一層處理。


MySQL大多數(shù)核心服務(wù)均在中間這一層,包括查詢解析、分析、優(yōu)化、緩存、內(nèi)置函數(shù)(比如:時(shí)間、數(shù)學(xué)、加密等函數(shù))。所有的跨存儲(chǔ)引擎的功能也在這一層實(shí)現(xiàn):存儲(chǔ)過(guò)程、觸發(fā)器、視圖等。


最下層為存儲(chǔ)引擎,其負(fù)責(zé)MySQL中的數(shù)據(jù)存儲(chǔ)和提取。和Linux下的文件系統(tǒng)類似,每種存儲(chǔ)引擎都有其優(yōu)勢(shì)和劣勢(shì)。中間的服務(wù)層通過(guò)API與存儲(chǔ)引擎通信,這些API接口屏蔽了不同存儲(chǔ)引擎間的差異。


MySQL查詢過(guò)程


我們總是希望MySQL能夠獲得更高的查詢性能,最好的辦法是弄清楚MySQL是如何優(yōu)化和執(zhí)行查詢的。一旦理解了這一點(diǎn),就會(huì)發(fā)現(xiàn):很多的查詢優(yōu)化工作實(shí)際上就是遵循一些原則讓MySQL的優(yōu)化器能夠按照預(yù)想的合理方式運(yùn)行而已。


當(dāng)向MySQL發(fā)送一個(gè)請(qǐng)求的時(shí)候,MySQL到底做了些什么呢?


MySQL查詢過(guò)程


客戶端/服務(wù)端通信協(xié)議


MySQL客戶端/服務(wù)端通信協(xié)議是“半雙工”的:在任一時(shí)刻,要么是服務(wù)器向客戶端發(fā)送數(shù)據(jù),要么是客戶端向服務(wù)器發(fā)送數(shù)據(jù),這兩個(gè)動(dòng)作不能同時(shí)發(fā)生。一旦一端開(kāi)始發(fā)送消息,另一端要接收完整個(gè)消息才能響應(yīng)它,所以我們無(wú)法也無(wú)須將一個(gè)消息切成小塊獨(dú)立發(fā)送,也沒(méi)有辦法進(jìn)行流量控制。


客戶端用一個(gè)單獨(dú)的數(shù)據(jù)包將查詢請(qǐng)求發(fā)送給服務(wù)器,所以當(dāng)查詢語(yǔ)句很長(zhǎng)的時(shí)候,需要設(shè)置max_allowed_packet參數(shù)。但是需要注意的是,如果查詢實(shí)在是太大,服務(wù)端會(huì)拒絕接收更多數(shù)據(jù)并拋出異常。


與之相反的是,服務(wù)器響應(yīng)給用戶的數(shù)據(jù)通常會(huì)很多,由多個(gè)數(shù)據(jù)包組成。但是當(dāng)服務(wù)器響應(yīng)客戶端請(qǐng)求時(shí),客戶端必須完整的接收整個(gè)返回結(jié)果,而不能簡(jiǎn)單的只取前面幾條結(jié)果,然后讓服務(wù)器停止發(fā)送。因而在實(shí)際開(kāi)發(fā)中,盡量保持查詢簡(jiǎn)單且只返回必需的數(shù)據(jù),減小通信間數(shù)據(jù)包的大小和數(shù)量是一個(gè)非常好的習(xí)慣,這也是查詢中盡量避免使用SELECT *以及加上LIMIT限制的原因之一。


查詢緩存


在解析一個(gè)查詢語(yǔ)句前,如果查詢緩存是打開(kāi)的,那么MySQL會(huì)檢查這個(gè)查詢語(yǔ)句是否命中查詢緩存中的數(shù)據(jù)。如果當(dāng)前查詢恰好命中查詢緩存,在檢查一次用戶權(quán)限后直接返回緩存中的結(jié)果。這種情況下,查詢不會(huì)被解析,也不會(huì)生成執(zhí)行計(jì)劃,更不會(huì)執(zhí)行。


MySQL將緩存存放在一個(gè)引用表(不要理解成table,可以認(rèn)為是類似于HashMap的數(shù)據(jù)結(jié)構(gòu)),通過(guò)一個(gè)哈希值索引,這個(gè)哈希值通過(guò)查詢本身、當(dāng)前要查詢的數(shù)據(jù)庫(kù)、客戶端協(xié)議版本號(hào)等一些可能影響結(jié)果的信息計(jì)算得來(lái)。所以兩個(gè)查詢?cè)谌魏巫址系牟煌ɡ纾嚎崭?、注釋),都?huì)導(dǎo)致緩存不會(huì)命中。


如果查詢中包含任何用戶自定義函數(shù)、存儲(chǔ)函數(shù)、用戶變量、臨時(shí)表、MySQL庫(kù)中的系統(tǒng)表,其查詢結(jié)果都不會(huì)被緩存。比如函數(shù)NOW()或者CURRENT_DATE()會(huì)因?yàn)椴煌牟樵儠r(shí)間,返回不同的查詢結(jié)果,再比如包含CURRENT_USER或者CONNECION_ID()的查詢語(yǔ)句會(huì)因?yàn)椴煌挠脩舳祷夭煌慕Y(jié)果,將這樣的查詢結(jié)果緩存起來(lái)沒(méi)有任何的意義。


既然是緩存,就會(huì)失效,那查詢緩存何時(shí)失效呢?MySQL的查詢緩存系統(tǒng)會(huì)跟蹤查詢中涉及的每個(gè)表,如果這些表(數(shù)據(jù)或結(jié)構(gòu))發(fā)生變化,那么和這張表相關(guān)的所有緩存數(shù)據(jù)都將失效。正因?yàn)槿绱?,在任何的寫操作時(shí),MySQL必須將對(duì)應(yīng)表的所有緩存都設(shè)置為失效。如果查詢緩存非常大或者碎片很多,這個(gè)操作就可能帶來(lái)很大的系統(tǒng)消耗,甚至導(dǎo)致系統(tǒng)僵死一會(huì)兒。而且查詢緩存對(duì)系統(tǒng)的額外消耗也不僅僅在寫操作,讀操作也不例外:


  1. 任何的查詢語(yǔ)句在開(kāi)始之前都必須經(jīng)過(guò)檢查,即使這條SQL語(yǔ)句永遠(yuǎn)不會(huì)命中緩存

  2. 如果查詢結(jié)果可以被緩存,那么執(zhí)行完成后,會(huì)將結(jié)果存入緩存,也會(huì)帶來(lái)額外的系統(tǒng)消耗


基于此,我們要知道并不是什么情況下查詢緩存都會(huì)提高系統(tǒng)性能,緩存和失效都會(huì)帶來(lái)額外消耗,只有當(dāng)緩存帶來(lái)的資源節(jié)約大于其本身消耗的資源時(shí),才會(huì)給系統(tǒng)帶來(lái)性能提升。但要如何評(píng)估打開(kāi)緩存是否能夠帶來(lái)性能提升是一件非常困難的事情,也不在本文討論的范疇內(nèi)。如果系統(tǒng)確實(shí)存在一些性能問(wèn)題,可以嘗試打開(kāi)查詢緩存,并在數(shù)據(jù)庫(kù)設(shè)計(jì)上做一些優(yōu)化,比如:


  1. 用多個(gè)小表代替一個(gè)大表,注意不要過(guò)度設(shè)計(jì)

  2. 批量插入代替循環(huán)單條插入

  3. 合理控制緩存空間大小,一般來(lái)說(shuō)其大小設(shè)置為幾十兆比較合適

  4. 可以通過(guò)SQL_CACHE和SQL_NO_CACHE來(lái)控制某個(gè)查詢語(yǔ)句是否需要進(jìn)行緩存


最后的忠告是不要輕易打開(kāi)查詢緩存,特別是寫密集型應(yīng)用。如果你實(shí)在是忍不住,可以將query_cache_type設(shè)置為DEMAND,這時(shí)只有加入SQL_CACHE的查詢才會(huì)走緩存,其他查詢則不會(huì),這樣可以非常自由地控制哪些查詢需要被緩存。


當(dāng)然查詢緩存系統(tǒng)本身是非常復(fù)雜的,這里討論的也只是很小的一部分,其他更深入的話題,比如:緩存是如何使用內(nèi)存的?如何控制內(nèi)存的碎片化?事務(wù)對(duì)查詢緩存有何影響等等,讀者可以自行閱讀相關(guān)資料,這里權(quán)當(dāng)拋磚引玉吧。


語(yǔ)法解析和預(yù)處理

MySQL通過(guò)關(guān)鍵字將SQL語(yǔ)句進(jìn)行解析,并生成一顆對(duì)應(yīng)的解析樹(shù)。這個(gè)過(guò)程解析器主要通過(guò)語(yǔ)法規(guī)則來(lái)驗(yàn)證和解析。比如SQL中是否使用了錯(cuò)誤的關(guān)鍵字或者關(guān)鍵字的順序是否正確等等。預(yù)處理則會(huì)根據(jù)MySQL規(guī)則進(jìn)一步檢查解析樹(shù)是否合法。比如檢查要查詢的數(shù)據(jù)表和數(shù)據(jù)列是否存在等。


查詢優(yōu)化


經(jīng)過(guò)前面的步驟生成的語(yǔ)法樹(shù)被認(rèn)為是合法的了,并且由優(yōu)化器將其轉(zhuǎn)化成查詢計(jì)劃。多數(shù)情況下,一條查詢可以有很多種執(zhí)行方式,最后都返回相應(yīng)的結(jié)果。優(yōu)化器的作用就是找到這其中最好的執(zhí)行計(jì)劃。


MySQL使用基于成本的優(yōu)化器,它嘗試預(yù)測(cè)一個(gè)查詢使用某種執(zhí)行計(jì)劃時(shí)的成本,并選擇其中成本最小的一個(gè)。在MySQL可以通過(guò)查詢當(dāng)前會(huì)話的last_query_cost的值來(lái)得到其計(jì)算當(dāng)前查詢的成本。



mysql> select * from t_message limit 10;

...省略結(jié)果集


mysql> show status like 'last_query_cost';

+-----------------+-------------+

| Variable_name   | Value       |

+-----------------+-------------+

| Last_query_cost | 6391.799000 |

+-----------------+-------------+


示例中的結(jié)果表示優(yōu)化器認(rèn)為大概需要做6391個(gè)數(shù)據(jù)頁(yè)的隨機(jī)查找才能完成上面的查詢。這個(gè)結(jié)果是根據(jù)一些列的統(tǒng)計(jì)信息計(jì)算得來(lái)的,這些統(tǒng)計(jì)信息包括:每張表或者索引的頁(yè)面?zhèn)€數(shù)、索引的基數(shù)、索引和數(shù)據(jù)行的長(zhǎng)度、索引的分布情況等等。


有非常多的原因會(huì)導(dǎo)致MySQL選擇錯(cuò)誤的執(zhí)行計(jì)劃,比如統(tǒng)計(jì)信息不準(zhǔn)確、不會(huì)考慮不受其控制的操作成本(用戶自定義函數(shù)、存儲(chǔ)過(guò)程)、MySQL認(rèn)為的最優(yōu)跟我們想的不一樣(我們希望執(zhí)行時(shí)間盡可能短,但MySQL值選擇它認(rèn)為成本小的,但成本小并不意味著執(zhí)行時(shí)間短)等等。


MySQL的查詢優(yōu)化器是一個(gè)非常復(fù)雜的部件,它使用了非常多的優(yōu)化策略來(lái)生成一個(gè)最優(yōu)的執(zhí)行計(jì)劃:


  • 重新定義表的關(guān)聯(lián)順序(多張表關(guān)聯(lián)查詢時(shí),并不一定按照SQL中指定的順序進(jìn)行,但有一些技巧可以指定關(guān)聯(lián)順序)

  • 優(yōu)化MIN()和MAX()函數(shù)(找某列的最小值,如果該列有索引,只需要查找B+Tree索引最左端,反之則可以找到最大值,具體原理見(jiàn)下文)

  • 提前終止查詢(比如:使用Limit時(shí),查找到滿足數(shù)量的結(jié)果集后會(huì)立即終止查詢)

  • 優(yōu)化排序(在老版本MySQL會(huì)使用兩次傳輸排序,即先讀取行指針和需要排序的字段在內(nèi)存中對(duì)其排序,然后再根據(jù)排序結(jié)果去讀取數(shù)據(jù)行,而新版本采用的是單次傳輸排序,也就是一次讀取所有的數(shù)據(jù)行,然后根據(jù)給定的列排序。對(duì)于I/O密集型應(yīng)用,效率會(huì)高很多)


隨著MySQL的不斷發(fā)展,優(yōu)化器使用的優(yōu)化策略也在不斷的進(jìn)化,這里僅僅介紹幾個(gè)非常常用且容易理解的優(yōu)化策略,其他的優(yōu)化策略,大家自行查閱吧。


查詢執(zhí)行引擎


在完成解析和優(yōu)化階段以后,MySQL會(huì)生成對(duì)應(yīng)的執(zhí)行計(jì)劃,查詢執(zhí)行引擎根據(jù)執(zhí)行計(jì)劃給出的指令逐步執(zhí)行得出結(jié)果。整個(gè)執(zhí)行過(guò)程的大部分操作均是通過(guò)調(diào)用存儲(chǔ)引擎實(shí)現(xiàn)的接口來(lái)完成,這些接口被稱為handler API。查詢過(guò)程中的每一張表由一個(gè)handler實(shí)例表示。實(shí)際上,MySQL在查詢優(yōu)化階段就為每一張表創(chuàng)建了一個(gè)handler實(shí)例,優(yōu)化器可以根據(jù)這些實(shí)例的接口來(lái)獲取表的相關(guān)信息,包括表的所有列名、索引統(tǒng)計(jì)信息等。存儲(chǔ)引擎接口提供了非常豐富的功能,但其底層僅有幾十個(gè)接口,這些接口像搭積木一樣完成了一次查詢的大部分操作。


返回結(jié)果給客戶端


查詢執(zhí)行的最后一個(gè)階段就是將結(jié)果返回給客戶端。即使查詢不到數(shù)據(jù),MySQL仍然會(huì)返回這個(gè)查詢的相關(guān)信息,比如該查詢影響到的行數(shù)以及執(zhí)行時(shí)間等。


如果查詢緩存被打開(kāi)且這個(gè)查詢可以被緩存,MySQL也會(huì)將結(jié)果存放到緩存中。


結(jié)果集返回客戶端是一個(gè)增量且逐步返回的過(guò)程。有可能MySQL在生成第一條結(jié)果時(shí),就開(kāi)始向客戶端逐步返回結(jié)果集了。這樣服務(wù)端就無(wú)須存儲(chǔ)太多結(jié)果而消耗過(guò)多內(nèi)存,也可以讓客戶端第一時(shí)間獲得返回結(jié)果。需要注意的是,結(jié)果集中的每一行都會(huì)以一個(gè)滿足①中所描述的通信協(xié)議的數(shù)據(jù)包發(fā)送,再通過(guò)TCP協(xié)議進(jìn)行傳輸,在傳輸過(guò)程中,可能對(duì)MySQL的數(shù)據(jù)包進(jìn)行緩存然后批量發(fā)送。


回頭總結(jié)一下MySQL整個(gè)查詢執(zhí)行過(guò)程,總的來(lái)說(shuō)分為6個(gè)步驟:


  • 客戶端向MySQL服務(wù)器發(fā)送一條查詢請(qǐng)求

  • 服務(wù)器首先檢查查詢緩存,如果命中緩存,則立刻返回存儲(chǔ)在緩存中的結(jié)果。否則進(jìn)入下一階段

  • 服務(wù)器進(jìn)行SQL解析、預(yù)處理、再由優(yōu)化器生成對(duì)應(yīng)的執(zhí)行計(jì)劃

  • MySQL根據(jù)執(zhí)行計(jì)劃,調(diào)用存儲(chǔ)引擎的API來(lái)執(zhí)行查詢

  • 將結(jié)果返回給客戶端,同時(shí)緩存查詢結(jié)果


性能優(yōu)化建議


看了這么多,你可能會(huì)期待給出一些優(yōu)化手段,是的,下面會(huì)從3個(gè)不同方面給出一些優(yōu)化建議。但請(qǐng)等等,還有一句忠告要先送給你:不要聽(tīng)信你看到的關(guān)于優(yōu)化的“絕對(duì)真理”,包括本文所討論的內(nèi)容,而應(yīng)該是在實(shí)際的業(yè)務(wù)場(chǎng)景下通過(guò)測(cè)試來(lái)驗(yàn)證你關(guān)于執(zhí)行計(jì)劃以及響應(yīng)時(shí)間的假設(shè)。


Scheme設(shè)計(jì)與數(shù)據(jù)類型優(yōu)化


選擇數(shù)據(jù)類型只要遵循小而簡(jiǎn)單的原則就好,越小的數(shù)據(jù)類型通常會(huì)更快,占用更少的磁盤、內(nèi)存,處理時(shí)需要的CPU周期也更少。越簡(jiǎn)單的數(shù)據(jù)類型在計(jì)算時(shí)需要更少的CPU周期,比如,整型就比字符操作代價(jià)低,因而會(huì)使用整型來(lái)存儲(chǔ)ip地址,使用DATETIME來(lái)存儲(chǔ)時(shí)間,而不是使用字符串。


這里總結(jié)幾個(gè)可能容易理解錯(cuò)誤的技巧:


  1. 通常來(lái)說(shuō)把可為NULL的列改為NOT NULL不會(huì)對(duì)性能提升有多少幫助,只是如果計(jì)劃在列上創(chuàng)建索引,就應(yīng)該將該列設(shè)置為NOT NULL。

  2. 對(duì)整數(shù)類型指定寬度,比如INT(11),沒(méi)有任何卵用。INT使用32位(4個(gè)字節(jié))存儲(chǔ)空間,那么它的表示范圍已經(jīng)確定,所以INT(1)和INT(20)對(duì)于存儲(chǔ)和計(jì)算是相同的。

  3. UNSIGNED表示不允許負(fù)值,大致可以使正數(shù)的上限提高一倍。比如TINYINT存儲(chǔ)范圍是-128 ~ 127,而UNSIGNED TINYINT存儲(chǔ)的范圍卻是0 - 255。

  4. 通常來(lái)講,沒(méi)有太大的必要使用DECIMAL數(shù)據(jù)類型。即使是在需要存儲(chǔ)財(cái)務(wù)數(shù)據(jù)時(shí),仍然可以使用BIGINT。比如需要精確到萬(wàn)分之一,那么可以將數(shù)據(jù)乘以一百萬(wàn)然后使用BIGINT存儲(chǔ)。這樣可以避免浮點(diǎn)數(shù)計(jì)算不準(zhǔn)確和DECIMAL精確計(jì)算代價(jià)高的問(wèn)題。

  5. TIMESTAMP使用4個(gè)字節(jié)存儲(chǔ)空間,DATETIME使用8個(gè)字節(jié)存儲(chǔ)空間。因而,TIMESTAMP只能表示1970 - 2038年,比DATETIME表示的范圍小得多,而且TIMESTAMP的值因時(shí)區(qū)不同而不同。

  6. 大多數(shù)情況下沒(méi)有使用枚舉類型的必要,其中一個(gè)缺點(diǎn)是枚舉的字符串列表是固定的,添加和刪除字符串(枚舉選項(xiàng))必須使用ALTER TABLE(如果只只是在列表末尾追加元素,不需要重建表)。

  7. schema的列不要太多。原因是存儲(chǔ)引擎的API工作時(shí)需要在服務(wù)器層和存儲(chǔ)引擎層之間通過(guò)行緩沖格式拷貝數(shù)據(jù),然后在服務(wù)器層將緩沖內(nèi)容解碼成各個(gè)列,這個(gè)轉(zhuǎn)換過(guò)程的代價(jià)是非常高的。如果列太多而實(shí)際使用的列又很少的話,有可能會(huì)導(dǎo)致CPU占用過(guò)高。

  8. 大表ALTER TABLE非常耗時(shí),MySQL執(zhí)行大部分修改表結(jié)果操作的方法是用新的結(jié)構(gòu)創(chuàng)建一個(gè)張空表,從舊表中查出所有的數(shù)據(jù)插入新表,然后再刪除舊表。尤其當(dāng)內(nèi)存不足而表又很大,而且還有很大索引的情況下,耗時(shí)更久。當(dāng)然有一些奇技巧可以解決這個(gè)問(wèn)題,有興趣可自行查閱。


創(chuàng)建高性能索引


索引是提高M(jìn)ySQL查詢性能的一個(gè)重要途徑,但過(guò)多的索引可能會(huì)導(dǎo)致過(guò)高的磁盤使用率以及過(guò)高的內(nèi)存占用,從而影響應(yīng)用程序的整體性能。應(yīng)當(dāng)盡量避免事后才想起添加索引,因?yàn)槭潞罂赡苄枰O(jiān)控大量的SQL才能定位到問(wèn)題所在,而且添加索引的時(shí)間肯定是遠(yuǎn)大于初始添加索引所需要的時(shí)間,可見(jiàn)索引的添加也是非常有技術(shù)含量的。


接下來(lái)將向你展示一系列創(chuàng)建高性能索引的策略,以及每條策略其背后的工作原理。但在此之前,先了解與索引相關(guān)的一些算法和數(shù)據(jù)結(jié)構(gòu),將有助于更好的理解后文的內(nèi)容。


索引相關(guān)的數(shù)據(jù)結(jié)構(gòu)和算法


通常我們所說(shuō)的索引是指B-Tree索引,它是目前關(guān)系型數(shù)據(jù)庫(kù)中查找數(shù)據(jù)最為常用和有效的索引,大多數(shù)存儲(chǔ)引擎都支持這種索引。使用B-Tree這個(gè)術(shù)語(yǔ),是因?yàn)镸ySQL在CREATE TABLE或其它語(yǔ)句中使用了這個(gè)關(guān)鍵字,但實(shí)際上不同的存儲(chǔ)引擎可能使用不同的數(shù)據(jù)結(jié)構(gòu),比如InnoDB就是使用的B+Tree。

B+Tree中的B是指balance,意為平衡。需要注意的是,B+樹(shù)索引并不能找到一個(gè)給定鍵值的具體行,它找到的只是被查找數(shù)據(jù)行所在的頁(yè),接著數(shù)據(jù)庫(kù)會(huì)把頁(yè)讀入到內(nèi)存,再在內(nèi)存中進(jìn)行查找,最后得到要查找的數(shù)據(jù)。


在介紹B+Tree前,先了解一下二叉查找樹(shù),它是一種經(jīng)典的數(shù)據(jù)結(jié)構(gòu),其左子樹(shù)的值總是小于根的值,右子樹(shù)的值總是大于根的值,如下圖①。如果要在這課樹(shù)中查找值為5的記錄,其大致流程:先找到根,其值為6,大于5,所以查找左子樹(shù),找到3,而5大于3,接著找3的右子樹(shù),總共找了3次。同樣的方法,如果查找值為8的記錄,也需要查找3次。所以二叉查找樹(shù)的平均查找次數(shù)為(3 + 3 + 3 + 2 + 2 + 1) / 6 = 2.3次,而順序查找的話,查找值為2的記錄,僅需要1次,但查找值為8的記錄則需要6次,所以順序查找的平均查找次數(shù)為:(1 + 2 + 3 + 4 + 5 + 6) / 6 = 3.3次,因此大多數(shù)情況下二叉查找樹(shù)的平均查找速度比順序查找要快。


二叉查找樹(shù)和平衡二叉樹(shù)


由于二叉查找樹(shù)可以任意構(gòu)造,同樣的值,可以構(gòu)造出如圖②的二叉查找樹(shù),顯然這棵二叉樹(shù)的查詢效率和順序查找差不多。若想二叉查找數(shù)的查詢性能最高,需要這棵二叉查找樹(shù)是平衡的,也即平衡二叉樹(shù)(AVL樹(shù))。


平衡二叉樹(shù)首先需要符合二叉查找樹(shù)的定義,其次必須滿足任何節(jié)點(diǎn)的兩個(gè)子樹(shù)的高度差不能大于1。顯然圖②不滿足平衡二叉樹(shù)的定義,而圖①是一課平衡二叉樹(shù)。平衡二叉樹(shù)的查找性能是比較高的(性能最好的是最優(yōu)二叉樹(shù)),查詢性能越好,維護(hù)的成本就越大。比如圖①的平衡二叉樹(shù),當(dāng)用戶需要插入一個(gè)新的值9的節(jié)點(diǎn)時(shí),就需要做出如下變動(dòng)。


平衡二叉樹(shù)旋轉(zhuǎn)


通過(guò)一次左旋操作就將插入后的樹(shù)重新變?yōu)槠胶舛鏄?shù)是最簡(jiǎn)單的情況了,實(shí)際應(yīng)用場(chǎng)景中可能需要旋轉(zhuǎn)多次。至此我們可以考慮一個(gè)問(wèn)題,平衡二叉樹(shù)的查找效率還不錯(cuò),實(shí)現(xiàn)也非常簡(jiǎn)單,相應(yīng)的維護(hù)成本還能接受,為什么MySQL索引不直接使用平衡二叉樹(shù)?


隨著數(shù)據(jù)庫(kù)中數(shù)據(jù)的增加,索引本身大小隨之增加,不可能全部存儲(chǔ)在內(nèi)存中,因此索引往往以索引文件的形式存儲(chǔ)的磁盤上。這樣的話,索引查找過(guò)程中就要產(chǎn)生磁盤I/O消耗,相對(duì)于內(nèi)存存取,I/O存取的消耗要高幾個(gè)數(shù)量級(jí)。可以想象一下一棵幾百萬(wàn)節(jié)點(diǎn)的二叉樹(shù)的深度是多少?如果將這么大深度的一顆二叉樹(shù)放磁盤上,每讀取一個(gè)節(jié)點(diǎn),需要一次磁盤的I/O讀取,整個(gè)查找的耗時(shí)顯然是不能夠接受的。那么如何減少查找過(guò)程中的I/O存取次數(shù)?


一種行之有效的解決方法是減少樹(shù)的深度,將二叉樹(shù)變?yōu)閙叉樹(shù)(多路搜索樹(shù)),而B(niǎo)+Tree就是一種多路搜索樹(shù)。理解B+Tree時(shí),只需要理解其最重要的兩個(gè)特征即可:第一,所有的關(guān)鍵字(可以理解為數(shù)據(jù))都存儲(chǔ)在葉子節(jié)點(diǎn)(Leaf Page),非葉子節(jié)點(diǎn)(Index Page)并不存儲(chǔ)真正的數(shù)據(jù),所有記錄節(jié)點(diǎn)都是按鍵值大小順序存放在同一層葉子節(jié)點(diǎn)上。其次,所有的葉子節(jié)點(diǎn)由指針連接。如下圖為高度為2的簡(jiǎn)化了的B+Tree。


簡(jiǎn)化B+Tree


怎么理解這兩個(gè)特征?MySQL將每個(gè)節(jié)點(diǎn)的大小設(shè)置為一個(gè)頁(yè)的整數(shù)倍(原因下文會(huì)介紹),也就是在節(jié)點(diǎn)空間大小一定的情況下,每個(gè)節(jié)點(diǎn)可以存儲(chǔ)更多的內(nèi)結(jié)點(diǎn),這樣每個(gè)結(jié)點(diǎn)能索引的范圍更大更精確。所有的葉子節(jié)點(diǎn)使用指針鏈接的好處是可以進(jìn)行區(qū)間訪問(wèn),比如上圖中,如果查找大于20而小于30的記錄,只需要找到節(jié)點(diǎn)20,就可以遍歷指針依次找到25、30。如果沒(méi)有鏈接指針的話,就無(wú)法進(jìn)行區(qū)間查找。這也是MySQL使用B+Tree作為索引存儲(chǔ)結(jié)構(gòu)的重要原因。


MySQL為何將節(jié)點(diǎn)大小設(shè)置為頁(yè)的整數(shù)倍,這就需要理解磁盤的存儲(chǔ)原理。磁盤本身存取就比主存慢很多,在加上機(jī)械運(yùn)動(dòng)損耗(特別是普通的機(jī)械硬盤),磁盤的存取速度往往是主存的幾百萬(wàn)分之一,為了盡量減少磁盤I/O,磁盤往往不是嚴(yán)格按需讀取,而是每次都會(huì)預(yù)讀,即使只需要一個(gè)字節(jié),磁盤也會(huì)從這個(gè)位置開(kāi)始,順序向后讀取一定長(zhǎng)度的數(shù)據(jù)放入內(nèi)存,預(yù)讀的長(zhǎng)度一般為頁(yè)的整數(shù)倍。


頁(yè)是計(jì)算機(jī)管理存儲(chǔ)器的邏輯塊,硬件及OS往往將主存和磁盤存儲(chǔ)區(qū)分割為連續(xù)的大小相等的塊,每個(gè)存儲(chǔ)塊稱為一頁(yè)(許多OS中,頁(yè)的大小通常為4K)。主存和磁盤以頁(yè)為單位交換數(shù)據(jù)。當(dāng)程序要讀取的數(shù)據(jù)不在主存中時(shí),會(huì)觸發(fā)一個(gè)缺頁(yè)異常,此時(shí)系統(tǒng)會(huì)向磁盤發(fā)出讀盤信號(hào),磁盤會(huì)找到數(shù)據(jù)的起始位置并向后連續(xù)讀取一頁(yè)或幾頁(yè)載入內(nèi)存中,然后一起返回,程序繼續(xù)運(yùn)行。


MySQL巧妙利用了磁盤預(yù)讀原理,將一個(gè)節(jié)點(diǎn)的大小設(shè)為等于一個(gè)頁(yè),這樣每個(gè)節(jié)點(diǎn)只需要一次I/O就可以完全載入。為了達(dá)到這個(gè)目的,每次新建節(jié)點(diǎn)時(shí),直接申請(qǐng)一個(gè)頁(yè)的空間,這樣就保證一個(gè)節(jié)點(diǎn)物理上也存儲(chǔ)在一個(gè)頁(yè)里,加之計(jì)算機(jī)存儲(chǔ)分配都是按頁(yè)對(duì)齊的,就實(shí)現(xiàn)了讀取一個(gè)節(jié)點(diǎn)只需一次I/O。假設(shè)B+Tree的高度為h,一次檢索最多需要h-1I/O(根節(jié)點(diǎn)常駐內(nèi)存),復(fù)雜度$O(h) = O(\log_{M}N)$。實(shí)際應(yīng)用場(chǎng)景中,M通常較大,常常超過(guò)100,因此樹(shù)的高度一般都比較小,通常不超過(guò)3。


最后簡(jiǎn)單了解下B+Tree節(jié)點(diǎn)的操作,在整體上對(duì)索引的維護(hù)有一個(gè)大概的了解,雖然索引可以大大提高查詢效率,但維護(hù)索引仍要花費(fèi)很大的代價(jià),因此合理的創(chuàng)建索引也就尤為重要。


仍以上面的樹(shù)為例,我們假設(shè)每個(gè)節(jié)點(diǎn)只能存儲(chǔ)4個(gè)內(nèi)節(jié)點(diǎn)。首先要插入第一個(gè)節(jié)點(diǎn)28,如下圖所示。


leaf page和index page都沒(méi)有滿


接著插入下一個(gè)節(jié)點(diǎn)70,在Index Page中查詢后得知應(yīng)該插入到50 - 70之間的葉子節(jié)點(diǎn),但葉子節(jié)點(diǎn)已滿,這時(shí)候就需要進(jìn)行也分裂的操作,當(dāng)前的葉子節(jié)點(diǎn)起點(diǎn)為50,所以根據(jù)中間值來(lái)拆分葉子節(jié)點(diǎn),如下圖所示。


Leaf Page拆分


最后插入一個(gè)節(jié)點(diǎn)95,這時(shí)候Index Page和Leaf Page都滿了,就需要做兩次拆分,如下圖所示。


Leaf Page與Index Page拆分


拆分后最終形成了這樣一顆樹(shù)。


最終樹(shù)


B+Tree為了保持平衡,對(duì)于新插入的值需要做大量的拆分頁(yè)操作,而頁(yè)的拆分需要I/O操作,為了盡可能的減少頁(yè)的拆分操作,B+Tree也提供了類似于平衡二叉樹(shù)的旋轉(zhuǎn)功能。當(dāng)Leaf Page已滿但其左右兄弟節(jié)點(diǎn)沒(méi)有滿的情況下,B+Tree并不急于去做拆分操作,而是將記錄移到當(dāng)前所在頁(yè)的兄弟節(jié)點(diǎn)上。通常情況下,左兄弟會(huì)被先檢查用來(lái)做旋轉(zhuǎn)操作。就比如上面第二個(gè)示例,當(dāng)插入70的時(shí)候,并不會(huì)去做頁(yè)拆分,而是左旋操作。


左旋操作


通過(guò)旋轉(zhuǎn)操作可以最大限度的減少頁(yè)分裂,從而減少索引維護(hù)過(guò)程中的磁盤的I/O操作,也提高索引維護(hù)效率。需要注意的是,刪除節(jié)點(diǎn)跟插入節(jié)點(diǎn)類似,仍然需要旋轉(zhuǎn)和拆分操作,這里就不再說(shuō)明。


高性能策略

通過(guò)上文,相信你對(duì)B+Tree的數(shù)據(jù)結(jié)構(gòu)已經(jīng)有了大致的了解,但MySQL中索引是如何組織數(shù)據(jù)的存儲(chǔ)呢?以一個(gè)簡(jiǎn)單的示例來(lái)說(shuō)明,假如有如下數(shù)據(jù)表:



CREATE TABLE People(

    last_name varchar(50) not null,

    first_name varchar(50) not null,

    dob date not null,

    gender enum(`m`,`f`) not null,

    key(last_name,first_name,dob)

);


對(duì)于表中每一行數(shù)據(jù),索引中包含了last_name、first_name、dob列的值,下圖展示了索引是如何組織數(shù)據(jù)存儲(chǔ)的。


索引如何組織數(shù)據(jù)存儲(chǔ),來(lái)自:高性能MySQL


可以看到,索引首先根據(jù)第一個(gè)字段來(lái)排列順序,當(dāng)名字相同時(shí),則根據(jù)第三個(gè)字段,即出生日期來(lái)排序,正是因?yàn)檫@個(gè)原因,才有了索引的“最左原則”。


1、MySQL不會(huì)使用索引的情況:非獨(dú)立的列


“獨(dú)立的列”是指索引列不能是表達(dá)式的一部分,也不能是函數(shù)的參數(shù)。比如:


select * from where id + 1 = 5


我們很容易看出其等價(jià)于 id = 4,但是MySQL無(wú)法自動(dòng)解析這個(gè)表達(dá)式,使用函數(shù)是同樣的道理。


2、前綴索引


如果列很長(zhǎng),通??梢运饕_(kāi)始的部分字符,這樣可以有效節(jié)約索引空間,從而提高索引效率。


3、多列索引和索引順序


在多數(shù)情況下,在多個(gè)列上建立獨(dú)立的索引并不能提高查詢性能。理由非常簡(jiǎn)單,MySQL不知道選擇哪個(gè)索引的查詢效率更好,所以在老版本,比如MySQL5.0之前就會(huì)隨便選擇一個(gè)列的索引,而新的版本會(huì)采用合并索引的策略。舉個(gè)簡(jiǎn)單的例子,在一張電影演員表中,在actor_id和film_id兩個(gè)列上都建立了獨(dú)立的索引,然后有如下查詢:


select film_id,actor_id from film_actor where actor_id = 1 or film_id = 1


老版本的MySQL會(huì)隨機(jī)選擇一個(gè)索引,但新版本做如下的優(yōu)化:


select film_id,actor_id from film_actor where actor_id = 1 

union all

select film_id,actor_id from film_actor where film_id = 1 and actor_id <> 1


  • 當(dāng)出現(xiàn)多個(gè)索引做相交操作時(shí)(多個(gè)AND條件),通常來(lái)說(shuō)一個(gè)包含所有相關(guān)列的索引要優(yōu)于多個(gè)獨(dú)立索引。

  • 當(dāng)出現(xiàn)多個(gè)索引做聯(lián)合操作時(shí)(多個(gè)OR條件),對(duì)結(jié)果集的合并、排序等操作需要耗費(fèi)大量的CPU和內(nèi)存資源,特別是當(dāng)其中的某些索引的選擇性不高,需要返回合并大量數(shù)據(jù)時(shí),查詢成本更高。所以這種情況下還不如走全表掃描。


因此explain時(shí)如果發(fā)現(xiàn)有索引合并(Extra字段出現(xiàn)Using union),應(yīng)該好好檢查一下查詢和表結(jié)構(gòu)是不是已經(jīng)是最優(yōu)的,如果查詢和表都沒(méi)有問(wèn)題,那只能說(shuō)明索引建的非常糟糕,應(yīng)當(dāng)慎重考慮索引是否合適,有可能一個(gè)包含所有相關(guān)列的多列索引更適合。


前面我們提到過(guò)索引如何組織數(shù)據(jù)存儲(chǔ)的,從圖中可以看到多列索引時(shí),索引的順序?qū)τ诓樵兪侵陵P(guān)重要的,很明顯應(yīng)該把選擇性更高的字段放到索引的前面,這樣通過(guò)第一個(gè)字段就可以過(guò)濾掉大多數(shù)不符合條件的數(shù)據(jù)。


索引選擇性是指不重復(fù)的索引值和數(shù)據(jù)表的總記錄數(shù)的比值,選擇性越高查詢效率越高,因?yàn)檫x擇性越高的索引可以讓MySQL在查詢時(shí)過(guò)濾掉更多的行。唯一索引的選擇性是1,這時(shí)最好的索引選擇性,性能也是最好的。


理解索引選擇性的概念后,就不難確定哪個(gè)字段的選擇性較高了,查一下就知道了,比如:


SELECT * FROM payment where staff_id = 2 and customer_id = 584


是應(yīng)該創(chuàng)建(staff_id,customer_id)的索引還是應(yīng)該顛倒一下順序?執(zhí)行下面的查詢,哪個(gè)字段的選擇性更接近1就把哪個(gè)字段索引前面就好。



select count(distinct staff_id)/count(*) as staff_id_selectivity,

       count(distinct customer_id)/count(*) as customer_id_selectivity,

       count(*) from payment


多數(shù)情況下使用這個(gè)原則沒(méi)有任何問(wèn)題,但仍然注意你的數(shù)據(jù)中是否存在一些特殊情況。舉個(gè)簡(jiǎn)單的例子,比如要查詢某個(gè)用戶組下有過(guò)交易的用戶信息:


select user_id from trade where user_group_id = 1 and trade_amount > 0


MySQL為這個(gè)查詢選擇了索引(user_group_id,trade_amount),如果不考慮特殊情況,這看起來(lái)沒(méi)有任何問(wèn)題,但實(shí)際情況是這張表的大多數(shù)數(shù)據(jù)都是從老系統(tǒng)中遷移過(guò)來(lái)的,由于新老系統(tǒng)的數(shù)據(jù)不兼容,所以就給老系統(tǒng)遷移過(guò)來(lái)的數(shù)據(jù)賦予了一個(gè)默認(rèn)的用戶組。這種情況下,通過(guò)索引掃描的行數(shù)跟全表掃描基本沒(méi)什么區(qū)別,索引也就起不到任何作用。


推廣開(kāi)來(lái)說(shuō),經(jīng)驗(yàn)法則和推論在多數(shù)情況下是有用的,可以指導(dǎo)我們開(kāi)發(fā)和設(shè)計(jì),但實(shí)際情況往往會(huì)更復(fù)雜,實(shí)際業(yè)務(wù)場(chǎng)景下的某些特殊情況可能會(huì)摧毀你的整個(gè)設(shè)計(jì)。


4、避免多個(gè)范圍條件


實(shí)際開(kāi)發(fā)中,我們會(huì)經(jīng)常使用多個(gè)范圍條件,比如想查詢某個(gè)時(shí)間段內(nèi)登錄過(guò)的用戶:


select user.* from user where login_time > '2017-04-01' and age between 18 and 30;


這個(gè)查詢有一個(gè)問(wèn)題:它有兩個(gè)范圍條件,login_time列和age列,MySQL可以使用login_time列的索引或者age列的索引,但無(wú)法同時(shí)使用它們。


5、覆蓋索引


如果一個(gè)索引包含或者說(shuō)覆蓋所有需要查詢的字段的值,那么就沒(méi)有必要再回表查詢,這就稱為覆蓋索引。覆蓋索引是非常有用的工具,可以極大的提高性能,因?yàn)椴樵冎恍枰獟呙杷饕龝?huì)帶來(lái)許多好處:


  • 索引條目遠(yuǎn)小于數(shù)據(jù)行大小,如果只讀取索引,極大減少數(shù)據(jù)訪問(wèn)量

  • 索引是有按照列值順序存儲(chǔ)的,對(duì)于I/O密集型的范圍查詢要比隨機(jī)從磁盤讀取每一行數(shù)據(jù)的IO要少的多


6、使用索引掃描來(lái)排序


MySQL有兩種方式可以生產(chǎn)有序的結(jié)果集,其一是對(duì)結(jié)果集進(jìn)行排序的操作,其二是按照索引順序掃描得出的結(jié)果自然是有序的。如果explain的結(jié)果中type列的值為index表示使用了索引掃描來(lái)做排序。


掃描索引本身很快,因?yàn)橹恍枰獜囊粭l索引記錄移動(dòng)到相鄰的下一條記錄。但如果索引本身不能覆蓋所有需要查詢的列,那么就不得不每掃描一條索引記錄就回表查詢一次對(duì)應(yīng)的行。這個(gè)讀取操作基本上是隨機(jī)I/O,因此按照索引順序讀取數(shù)據(jù)的速度通常要比順序地全表掃描要慢。


在設(shè)計(jì)索引時(shí),如果一個(gè)索引既能夠滿足排序,又滿足查詢,是最好的。


只有當(dāng)索引的列順序和ORDER BY子句的順序完全一致,并且所有列的排序方向也一樣時(shí),才能夠使用索引來(lái)對(duì)結(jié)果做排序。如果查詢需要關(guān)聯(lián)多張表,則只有ORDER BY子句引用的字段全部為第一張表時(shí),才能使用索引做排序。ORDER BY子句和查詢的限制是一樣的,都要滿足最左前綴的要求(有一種情況例外,就是最左的列被指定為常數(shù),下面是一個(gè)簡(jiǎn)單的示例),其它情況下都需要執(zhí)行排序操作,而無(wú)法利用索引排序。



// 最左列為常數(shù),索引:(date,staff_id,customer_id)

select  staff_id,customer_id from demo where date = '2015-06-01' order by staff_id,customer_id


7、冗余和重復(fù)索引


冗余索引是指在相同的列上按照相同的順序創(chuàng)建的相同類型的索引,應(yīng)當(dāng)盡量避免這種索引,發(fā)現(xiàn)后立即刪除。比如有一個(gè)索引(A,B),再創(chuàng)建索引(A)就是冗余索引。冗余索引經(jīng)常發(fā)生在為表添加新索引時(shí),比如有人新建了索引(A,B),但這個(gè)索引不是擴(kuò)展已有的索引(A)。


大多數(shù)情況下都應(yīng)該盡量擴(kuò)展已有的索引而不是創(chuàng)建新索引。但有極少情況下出現(xiàn)性能方面的考慮需要冗余索引,比如擴(kuò)展已有索引而導(dǎo)致其變得過(guò)大,從而影響到其他使用該索引的查詢。


8、刪除長(zhǎng)期未使用的索引


定期刪除一些長(zhǎng)時(shí)間未使用過(guò)的索引是一個(gè)非常好的習(xí)慣。


關(guān)于索引這個(gè)話題打算就此打住,最后要說(shuō)一句,索引并不總是最好的工具,只有當(dāng)索引幫助提高查詢速度帶來(lái)的好處大于其帶來(lái)的額外工作時(shí),索引才是有效的。對(duì)于非常小的表,簡(jiǎn)單的全表掃描更高效。對(duì)于中到大型的表,索引就非常有效。對(duì)于超大型的表,建立和維護(hù)索引的代價(jià)隨之增長(zhǎng),這時(shí)候其他技術(shù)也許更有效,比如分區(qū)表。最后的最后,explain后再提測(cè)是一種美德。


優(yōu)化COUNT()查詢


COUNT()可能是被大家誤解最多的函數(shù)了,它有兩種不同的作用,其一是統(tǒng)計(jì)某個(gè)列值的數(shù)量,其二是統(tǒng)計(jì)行數(shù)。統(tǒng)計(jì)列值時(shí),要求列值是非空的,它不會(huì)統(tǒng)計(jì)NULL。如果確認(rèn)括號(hào)中的表達(dá)式不可能為空時(shí),實(shí)際上就是在統(tǒng)計(jì)行數(shù)。最簡(jiǎn)單的就是當(dāng)使用COUNT(*)時(shí),并不是我們所想象的那樣擴(kuò)展成所有的列,實(shí)際上,它會(huì)忽略所有的列而直接統(tǒng)計(jì)所有的行數(shù)。


我們最常見(jiàn)的誤解也就在這兒,在括號(hào)內(nèi)指定了一列卻希望統(tǒng)計(jì)結(jié)果是行數(shù),而且還常常誤以為前者的性能會(huì)更好。但實(shí)際并非這樣,如果要統(tǒng)計(jì)行數(shù),直接使用COUNT(*),意義清晰,且性能更好。


有時(shí)候某些業(yè)務(wù)場(chǎng)景并不需要完全精確的COUNT值,可以用近似值來(lái)代替,EXPLAIN出來(lái)的行數(shù)就是一個(gè)不錯(cuò)的近似值,而且執(zhí)行EXPLAIN并不需要真正地去執(zhí)行查詢,所以成本非常低。通常來(lái)說(shuō),執(zhí)行COUNT()都需要掃描大量的行才能獲取到精確的數(shù)據(jù),因此很難優(yōu)化,MySQL層面還能做得也就只有覆蓋索引了。如果不還能解決問(wèn)題,只有從架構(gòu)層面解決了,比如添加匯總表,或者使用redis這樣的外部緩存系統(tǒng)。

優(yōu)化關(guān)聯(lián)查詢


在大數(shù)據(jù)場(chǎng)景下,表與表之間通過(guò)一個(gè)冗余字段來(lái)關(guān)聯(lián),要比直接使用JOIN有更好的性能。如果確實(shí)需要使用關(guān)聯(lián)查詢的情況下,需要特別注意的是:


  1. 確保ON和USING字句中的列上有索引。在創(chuàng)建索引的時(shí)候就要考慮到關(guān)聯(lián)的順序。當(dāng)表A和表B用列c關(guān)聯(lián)的時(shí)候,如果優(yōu)化器關(guān)聯(lián)的順序是A、B,那么就不需要在A表的對(duì)應(yīng)列上創(chuàng)建索引。沒(méi)有用到的索引會(huì)帶來(lái)額外的負(fù)擔(dān),一般來(lái)說(shuō),除非有其他理由,只需要在關(guān)聯(lián)順序中的第二張表的相應(yīng)列上創(chuàng)建索引(具體原因下文分析)。

  2. 確保任何的GROUP BY和ORDER BY中的表達(dá)式只涉及到一個(gè)表中的列,這樣MySQL才有可能使用索引來(lái)優(yōu)化。


要理解優(yōu)化關(guān)聯(lián)查詢的第一個(gè)技巧,就需要理解MySQL是如何執(zhí)行關(guān)聯(lián)查詢的。當(dāng)前MySQL關(guān)聯(lián)執(zhí)行的策略非常簡(jiǎn)單,它對(duì)任何的關(guān)聯(lián)都執(zhí)行嵌套循環(huán)關(guān)聯(lián)操作,即先在一個(gè)表中循環(huán)取出單條數(shù)據(jù),然后在嵌套循環(huán)到下一個(gè)表中尋找匹配的行,依次下去,直到找到所有表中匹配的行為為止。然后根據(jù)各個(gè)表匹配的行,返回查詢中需要的各個(gè)列。


太抽象了?以上面的示例來(lái)說(shuō)明,比如有這樣的一個(gè)查詢:


SELECT A.xx,B.yy

FROM A INNER JOIN B USING(c)

WHERE A.xx IN (5,6)


假設(shè)MySQL按照查詢中的關(guān)聯(lián)順序A、B來(lái)進(jìn)行關(guān)聯(lián)操作,那么可以用下面的偽代碼表示MySQL如何完成這個(gè)查詢:


outer_iterator = SELECT A.xx,A.c FROM A WHERE A.xx IN (5,6);

outer_row = outer_iterator.next;

while(outer_row) {

    inner_iterator = SELECT B.yy FROM B WHERE B.c = outer_row.c;

    inner_row = inner_iterator.next;

    while(inner_row) {

        output[inner_row.yy,outer_row.xx];

        inner_row = inner_iterator.next;

    }

    outer_row = outer_iterator.next;

}


可以看到,最外層的查詢是根據(jù)A.xx列來(lái)查詢的,A.c上如果有索引的話,整個(gè)關(guān)聯(lián)查詢也不會(huì)使用。再看內(nèi)層的查詢,很明顯B.c上如果有索引的話,能夠加速查詢,因此只需要在關(guān)聯(lián)順序中的第二張表的相應(yīng)列上創(chuàng)建索引即可。


優(yōu)化LIMIT分頁(yè)


當(dāng)需要分頁(yè)操作時(shí),通常會(huì)使用LIMIT加上偏移量的辦法實(shí)現(xiàn),同時(shí)加上合適的ORDER BY字句。如果有對(duì)應(yīng)的索引,通常效率會(huì)不錯(cuò),否則,MySQL需要做大量的文件排序操作。


一個(gè)常見(jiàn)的問(wèn)題是當(dāng)偏移量非常大的時(shí)候,比如:LIMIT 10000 20這樣的查詢,MySQL需要查詢10020條記錄然后只返回20條記錄,前面的10000條都將被拋棄,這樣的代價(jià)非常高。


優(yōu)化這種查詢一個(gè)最簡(jiǎn)單的辦法就是盡可能的使用覆蓋索引掃描,而不是查詢所有的列。然后根據(jù)需要做一次關(guān)聯(lián)查詢?cè)俜祷厮械牧?。?duì)于偏移量很大時(shí),這樣做的效率會(huì)提升非常大??紤]下面的查詢:


SELECT film_id,description FROM film ORDER BY title LIMIT 50,5;


如果這張表非常大,那么這個(gè)查詢最好改成下面的樣子:


SELECT film.film_id,film.description

FROM film INNER JOIN (

    SELECT film_id FROM film ORDER BY title LIMIT 50,5

) AS tmp USING(film_id);


這里的延遲關(guān)聯(lián)將大大提升查詢效率,讓MySQL掃描盡可能少的頁(yè)面,獲取需要訪問(wèn)的記錄后在根據(jù)關(guān)聯(lián)列回原表查詢所需要的列。


有時(shí)候如果可以使用書(shū)簽記錄上次取數(shù)據(jù)的位置,那么下次就可以直接從該書(shū)簽記錄的位置開(kāi)始掃描,這樣就可以避免使用OFFSET,比如下面的查詢:


SELECT id FROM t LIMIT 10000, 10;


改為:


SELECT id FROM t WHERE id > 10000 LIMIT 10;


其它優(yōu)化的辦法還包括使用預(yù)先計(jì)算的匯總表,或者關(guān)聯(lián)到一個(gè)冗余表,冗余表中只包含主鍵列和需要做排序的列。


優(yōu)化UNION


MySQL處理UNION的策略是先創(chuàng)建臨時(shí)表,然后再把各個(gè)查詢結(jié)果插入到臨時(shí)表中,最后再來(lái)做查詢。因此很多優(yōu)化策略在UNION查詢中都沒(méi)有辦法很好的時(shí)候。經(jīng)常需要手動(dòng)將WHERE、LIMIT、ORDER BY等字句“下推”到各個(gè)子查詢中,以便優(yōu)化器可以充分利用這些條件先優(yōu)化。


除非確實(shí)需要服務(wù)器去重,否則就一定要使用UNION ALL,如果沒(méi)有ALL關(guān)鍵字,MySQL會(huì)給臨時(shí)表加上DISTINCT選項(xiàng),這會(huì)導(dǎo)致整個(gè)臨時(shí)表的數(shù)據(jù)做唯一性檢查,這樣做的代價(jià)非常高。當(dāng)然即使使用ALL關(guān)鍵字,MySQL總是將結(jié)果放入臨時(shí)表,然后再讀出,再返回給客戶端。雖然很多時(shí)候沒(méi)有這個(gè)必要,比如有時(shí)候可以直接把每個(gè)子查詢的結(jié)果返回給客戶端。


結(jié)語(yǔ)

理解查詢是如何執(zhí)行以及時(shí)間都消耗在哪些地方,再加上一些優(yōu)化過(guò)程的知識(shí),可以幫助大家更好的理解MySQL,理解常見(jiàn)優(yōu)化技巧背后的原理。希望本文中的原理、示例能夠幫助大家更好的將理論和實(shí)踐聯(lián)系起來(lái),更多的將理論知識(shí)運(yùn)用到實(shí)踐中。


其他也沒(méi)啥說(shuō)的了,給大家留兩個(gè)思考題吧,可以在腦袋里想想答案,這也是大家經(jīng)常掛在嘴邊的,但很少有人會(huì)思考為什么?


  1. 有非常多的程序員在分享時(shí)都會(huì)拋出這樣一個(gè)觀點(diǎn):盡可能不要使用存儲(chǔ)過(guò)程,存儲(chǔ)過(guò)程非常不容易維護(hù),也會(huì)增加使用成本,應(yīng)該把業(yè)務(wù)邏輯放到客戶端。既然客戶端都能干這些事,那為什么還要存儲(chǔ)過(guò)程?

  2. JOIN本身也挺方便的,直接查詢就好了,為什么還需要視圖呢?


參考資料

  1. 姜承堯 著;MySQL技術(shù)內(nèi)幕-InnoDB存儲(chǔ)引擎;機(jī)械工業(yè)出版社,2013

  2. Baron Scbwartz 等著;寧海元 周振興等譯;高性能MySQL(第三版); 電子工業(yè)出版社, 2013

  3. 由 B-/B+樹(shù)看 MySQL索引結(jié)構(gòu)

    https://segmentfault.com/a/1190000004690721




如果Docker命令還記不住,就對(duì)不起這發(fā)量了??!


漫話:如何給女朋友解釋為什么不能在MySQL中使用UTF-8編碼


瀏覽 36
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評(píng)論
圖片
表情
推薦
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 日韩免费看| 午夜操逼| 97自拍视频| 久久6精品| 韩国成人免费无码免费视频| 中文字幕的色| 日韩天堂在线| 人妻无码久久| ChineSe露脸老女人| 久久国产精| 人人爽人人爽| 亚洲成人日韩| 欧美色插| 国精产品一区一区三区有限公司杨 | 日韩中文字幕一区| 神马午夜| 粉嫩av在线| 9久精品| 68久久久| 安徽妇女BBBWBBBwm| 亚洲成人大香蕉| a级片在线观看| 一区二区三区四区日韩| 最近中文字幕高清2019中文字幕 | 无码中文字幕| 国产AV无码成人精品毛片| 黄色一级免费电影| 特级西西人体444www高清大胆 | 北条麻妃电影九九九| 五月综合色| 成人精品久久| 婷婷丁香五月亚洲| 特级444www| 中文字幕精品综合| 日韩人妻无码一区二区三区七区 | 男女69视频| 亚洲国产电影| 天天日天天| 免费毛片观看| 成人精品一区二区三区视频| 天天日天天草| 91久久国产性奴调教| 亚洲精品中文字幕在线观看| 九色首页| 国产久久性爱| 日韩xxx视频| AV三级无码| 成人亚洲| 99er热精品视频| 99热r| 黄色免费观看网站| 国产小精品| 无码观看视频| 日韩五月婷婷| 伊人成人视频在线观看| 波多野结衣高清无码| 亚洲资源网| 人妻无码高清| 欧美黄色三级视频| 91嫩操| 干老女人逼| 3DAV一区二区三区动漫| 无码人妻日本| 少妇精品| 国产成人午夜| 韩国一级av| 先锋影音资源网站| 欧美成人精品欧美一级| 免费无码国产在线55| 在线观看亚| 港澳日韩黄片| 日本少妇bbw| 欧美性爱香蕉视频| 日韩人妻精品无码| 久久免费精品视频| 日韩人妻无码一区二区| AV网站在线播放| 欧美Aⅴ| 大香蕉东京热| 天天操夜夜爱| 中文字幕第23页| 91在线观看网站| 一级成人电影| 熟妇高潮一区二区高潮| 99久久精品一区二区成人| 欧美激情四射老司机| 91视频在线观看免费| 在线中文字幕视频| 四虎AV| 亚洲性爱无码| 91人妻无码精品一区二区| 大荫蒂hd大荫蒂视频| 黄色小说视频网站| 成人高清无码在线观看| 久久无码一区二区| 少妇推油呻吟白浆啪啪成人片| 日韩精品成人av| 青青草原国产视频| 懂色av懂色av粉嫩av| 青娱乐一级无码| 看免费黄色录像| 黄色九九| 吴梦梦一区二区三区| 日韩av三级在线观看| 日韩在线观看免费| 成人黄色网| 久久高清免费视频| 日本中文字幕在线观看| 日韩毛片视频| 九九热精品视频| 一级片A片| 中文字幕日韩有码| 爱爱无码| 美国久久久| 日韩欧美色| 国产高清精品软件丝瓜软件| 国产一页| 三级片久久久| 日本中文字幕无码| 人人澡人人澡| 欧一美一婬一伦一区| 麻豆操逼| 波多野结衣不卡| 国内操逼视频| 无码视频免费| 人妻人人操| 97人妻| 日韩无码高清视频| 91爱爱| 青草中文娱乐网在线| 日韩欧美在线观看| 懂色午夜福利一区二区三区| 日日夜夜爽歪歪| 日韩美女免费性爱视频| 午夜av免费在线| 久草网大香蕉| 日无码在线| 91黄色在线视频| 亚洲成人福利电影| 91精品国产乱码| 亚洲无码午夜| 免费无码婬片AAAAA片| 成年片免费观看网站免费观看,亚洲+欧... | 俺来俺也去| 亚洲一级视频在线观看| 91黑人| 国产三级在线观看视频| 日韩一级高清| 先锋影音AV在线| 性久久久久久| 91精品人妻一区二区三区| 亚洲中文字| 国产成人精品AV| 在线观看无码视频| 黄色视频在线免费观看高清视频| 人妻超碰在线| 成人毛片在线大全免费| 免费人成视频观看| 男人的天堂久久| 色噜噜人妻av中文字幕| 欧美老妇操逼视频| 免费观看黄色小视频| 大香蕉伊人在线网| 中文字幕日韩在线视频| 中文字幕乱码人妻二区三区| GOGO人体做爰大胆视频| 日韩中文字幕在线观看| 成人三级黄色| 操B视频在线| 爱搞搞就要爱搞搞| 粉嫩99精品99久久久久久特污| 亚洲乱伦图| 刘玥一区二区| 91吴梦梦一区二区传媒| 无码人妻系列| 国产精品久久久精品| 悠悠色影院| 久久久成人网| 天天色伊人| 综合狠狠| 无码AV电影在线观看| 2024AV在线| AV福利在线观看| 亚洲欧美高清| 麻豆视频一区二区| 国产精品内射婷婷一级二| 91国产精品| 欧美伊人网在线观看| 99热精品免费观看| 日本免费黄| 国产a片免费看| 青春草在线视频观看| 秋霞A片| 日韩不卡在线| 国产欧美二区综合中文字幕精品一 | 99热免费精品| www.俺去了| 男女啪啪动态图| www.水蜜桃| 亚洲欧美色图| 2025天天操夜夜操| 你懂得在线视频| 日本成人黄色电影| 欧美特黄AAAAAAAAA片| 操毛| 亚洲AV成人片色在线观看麻豆 | 国产精品自拍视频| 国产精品久久久无码专区| 成人动漫免费观看| 91羞羞| 黄色AV免费在线观看| 18禁黄网| 大香蕉综合网| 伊人二区| 国产一级自拍| 久久国际精品| 天天视频色| 91小电影| 成人亚洲天堂| 激情国产AV| 伊人久久视频| 日韩一区无码| 麻豆天美传媒AV果冻传媒| 欧美日韩一区二区三区视频| 亚洲天堂在线免费| 亚洲黄色免费电影| 日韩黄片视频| 日韩美女免费性爱视频| 无码人妻丰满熟妇区17水蜜桃| 欧美国产综合在线| 色婷婷AV一区二区三区之e本道| 精品人妻一区| 网络自拍亚洲激情| 97精产国品久久蜜桃臀| 久久午夜无码鲁丝片主演是谁| 亚洲免费观看高清完整版在线| 天天干B| 翔田千里被操120分钟| 日韩一区二区三区在线视频| 噜噜噜久久久| 欧美日逼网站| 三级片无码| free性欧美| 人人操人人爽人人妻| 99视频这里有精品| 成人在线一区二区| 囯产精品久久久久久久久久久久久久 | 波多野结衣网址| 麻豆毛片| 欧美性爱18| 欧美成人精品激情在线视频| 国产高清免费视频| 久操精品视频| 大香蕉午夜视频| 中文字幕av久久爽Av| 俺去俺来也www色视频| 国产精品久久久91| 学生妹一级片内射视频| 操操网| 亚洲成人一区二区三区| 国产精品99久久免费黑人人妻| 亚洲人成电影网| 国产成人高潮毛片| 一级片在线| 久久六六| 99性爱视频| 亚洲avwww| 伊人99| 黑吊操| ThePorn精品无码| 欧美精品一级片| 91视频网站在线观看| 99热中文字幕在线观看| 91久久午夜无码鲁丝片久久人妻| 中国黄色大片| 一插菊花综合网| 操逼首页123| 三级午夜在线无码| 91免费网站| 国产免看一级a一片成人aⅴ| 大香蕉天天操| 91在线播放视频| 国产乱婬片视频| 国产一级自拍| 人人操人人草| 久久久久久国际四虎免费精品视频| 亚洲熟妇在线观看| 国产麻豆三级片| 大香蕉一区二区三区| 五月天AV网站| 99久久性爱| 99美女精品视频| 中国熟睡妇BBwBBw| 国产伊人影院| 91中文字幕在线观看| 亚洲AV中文在线| 中文字幕一区二区三区日本在线 | 中文字幕第10页| 午夜aaa| AV大全在线免费观看| 色色色色五月| 毛片在线视频| 四川BBB操BBB| 蜜臀av一区二区| 久久精品在线观看| 丁香久久婷婷| 99在线精品视频免费观看20| 狠狠干亚洲视频| 亚洲一线视频| 福利网站在线观看| 成人禁区| 男人天堂免费视频| 欧美午夜成人| 成人精品永久免费视频99久久精品| 黑人精品欧美一区二区蜜桃 | 国产亚洲无码激情前后夹击| 欧美熟妇性爱| 999高清无码| 日韩无码黄| 日本精品国产| 精品免费黄色视频| 国产视频一区二区在线观看 | 国产成人网站免费观看| 无码免费婬AV片在线观看| 亚洲精品系列| 最新色站| 婷婷开心色四房播播在线| 中文字幕视频免费| 狠狠操综合网| 美女网站永久免费观看| 91AV| 亚洲无吗在线播放| 青青草中文字幕| 浪潮在线观看完整版| 国产毛片精品一区二区色欲黄A片 另类老妇奶性BBWBBwBBw | 亚洲日韩视频在线观看| 777米奇视频| AV资源在线播放| 亚洲vs无码蜜桃少妇| 日韩AV小电影| 亚洲AV无码成人精品区天堂小说| 少妇探花| 加勒比无码在线| 好男人一区二区三区在线观看 | 国产乱子伦真实精品!| 久久女人视频| 黄色成人网站大全| 青青草在线播放| 亚洲性爱手机版| 又大又粗又爽| 人人操在线公开| 先锋资源一区| 在线观看亚洲| 久久精品秘一区二区三免费| 少妇一区二区三区| 欧美久操| 亚洲AV无码专区一级婬片毛片| www日本在线| 精品在线免费视频| 国产亚洲视频免费观看| 无码精品人妻一区二区三区漫画| 120分钟婬片免费看| 免费A网站| 激情小视频在线| 日韩成人无码专区| 成人精品网| 伊人春色av| 天天射夜夜骑| 哪里能看毛片| 久久无码成人| 十八禁无码网站在线观看| 国产精品毛片A√一区| 91吴梦梦一区二区传媒| 国产三级性爱| 午夜精品视频在线观看| 中文字幕视频一区| 美女操逼网站| 六月婷婷在线观看| 五月天激情综合网| 嫩草99| 亚洲高清无码在线播放| 120分钟婬片免费看| 青青超碰| 色久影院| 国产一级a毛一级做a爱| 在线观看中文字幕网站| 91免费视频在线| 国产精品国产三级囯产普通话2| 激情网站在线观看| 天堂在线中文网| 九九成人| 操操操综合网| 日本人人操人人摸| 成人动漫在线观看| 午夜成人大片| 思思操| www.无码视频| 久久久久久久性爱| 91大吊| 欧美在线中文字幕| 激情五月激情综合网| 男女拍拍视频| 国产一级AV片| 成人a片在线免费观看| 91青青草视频| 国产免费AV片在线无码| 国产精品国产成人国产三级| 无套内射在线| 黄色精品视频| 韩国三级HD中文字幕的背景音乐| 亚洲成人少妇老妇a视频在线| 狼人香蕉网| 亚洲性爱综合| 韩国无码专区| 国产成人片在线观看| 欧美性爱一区二区| 亚洲欧美成人在线视频| 色秘乱码一区二区三区| 新妺妺窝窝777777野外| 青春草在线免费观看| 国产一区二区视频在线观看| 成人网站大香蕉| 少妇无码视频| 俺去也www俺去也com| 国产午夜91人妻| av手机版| 午夜国产在线视频| 欧美视频在线播放| 日本狠狠干| 99青青草| 天天综合网久久综合网| 黄色小视频免费| 激情在线视频| 人操人妻| 欧美色色影院| 靠逼免费视频| 91无码精品国产AⅤ| 麻豆一级片| 一级A片免费| 91久久久久久久91| 翔田千里一区二区| 九九热视频在线| 日鸡吧链接| 国产在线视频一区二区| 国产suv精品一区二区6精华液 | 快播激情小说| 97香蕉久久国产超碰青草专区| 亚洲精品黄色电影| 操逼免费看| 亚洲中文综合| 九九热日本| 99精品免费视频| 成人午夜福利高清视频| 亚洲精品无码在线播放| 中日韩一级片| 国产99久久久| 一本视频| 婷婷五月天综合网| 色婷婷五月天激情| 国产一级影院| 午夜精品18视频国产17c| 天天日天天色天天干| 国产欧美综合精品| 国产成人无码区免费AV片在线| 激情五月天综合网| 日韩一级| 中文无码日韩欧美久久| 亚洲偷拍视频| 日韩在线观看网站| 黄色影片在线观看| 在线亚洲AV| 国产毛片久久久久久国产毛片| 人人干人人干人人| 永久免费AV| 青草成人在线视频| 黃色一级A一片人与| 国产激情在线| 91中文| 国产乱码一区二区三区四区在线| 国产av地址| 日韩色妇| 色婷婷激情AV| 久久不卡视频| 一本一道久久综合| 国产AV18岁| 91九色在线观看| 欧性猛交ⅩXXX乱大交| 日韩中文字幕一区二区三区| 午夜香蕉视频| 国产36页| 69激情网| 中文字幕乱伦| 国产精品成人99一区无码| 91欧美精品成人综合在线观看 | 欧美香蕉视频| 大香蕉伊人色| 午夜国产精品AV| 婷婷五月天综合网| 十八禁视频在线观看网站.www | 国产精品视频一区二区三区在线观看| 在线观看黄网| 四虎成人无码| 免费高清无码视频在线观看| 另类av| 欧美插穴| 二区三区免费| 中日韩精品A片中文字幕| 色婷婷视频在线观看| 国产四区| 操逼视频电影| 国产女同在线观看| 国产va| 大香蕉伊人在线网| 伊人青青操| 国产成人视频在线播放| 成人三级电影| 亚洲国产成人电影| 色天堂污| 六月婷婷七月丁香| 国产操B| 无码人妻一区二区三区免费n狂飙 性猛交AAAA片免费看蜜桃视频 | 91最新视频| 免费黄色在线观看| 激情AV| 99精品全国免费观看| 日逼大香蕉| 啪啪成人网| 国产十八岁在线观看| 国产操逼免费视频| 大肉大捧一进一出免费阅读| 天堂资源地址在线| 日韩欧美成人电影| 九色PORN视频成人蝌蚪自拍| 国产黄色片在线播放| 亚洲v欧美v| 人妻少妇精品视频| 东方AV在线播放| 色五月激情网| 91伊人久热精品| 日韩美女免费性爱视频| 国产在线不卡| 99热3| 麻豆传媒免费观看| 精品国产午夜福利| 三级免费无限AV| 无码人妻丰满熟妇区毛片视频| 黄色国产AV| 成人永久免费视频| 天天综合网久久| 操逼视频免费| 欧美手机在线视频| 躁BBB躁BBB躁BBBBB乃| 国产九九九九九九| 亚洲视频在线观| 黄色777| 无码精品ThePorn| 婷婷五月天成人电影| BBB搡BBB搡BBB搡BBB| 操逼人妻| 呦呦av| 无码高清视频| 麻豆啪啪| 精品久久ai| 成人午夜福利高清视频| 精品一区二区三区四区五区| 四川妇搡BBBB搡BBBB| 国产日韩欧美在线| 欧美另类激情| 91欧美精品| 乱子伦毛片国产| 黄色片成人| 三级av网站| 永井玛丽亚av无码中出流出| 天天操天天拍| 2025天天干| 超碰97免费在线| 国产一区二区三区免费观看| 日韩免费性爱视频| 日韩二| 日韩一级片在线观看| 久久久999久久久999精神| 不卡视频一区二区| 三级片网站在线观看| 成人激情四射网| 免费69视频| 免费看毛片中文字幕| 久操大香蕉| 色老汉视频| 2025AV天堂| 123好逼网| 精品亚洲一区二区三区四区五区 | 成人网站视频在线观看| 精品国产AⅤ麻豆| 亚洲AV网址| 欧美日韩国产性爱| 夜操操| 在线观看亚洲无码视频| 一本久久A精品一合区久久久 | 天天操网| 欧美性爱免费网站| 成人免费看片| 久久新视频| 精品亚洲一区二区三区四区五区| 97人妻精品一区二区三区软件| 亚洲欧美激情小说另类| www.色欲av| 日韩精品| av中文字幕网| JLZZJLZZ亚洲女人| 潮喷在线观看| 亚洲在线中文| 亚洲黄色在线观看视频| 亚洲黄色激情| 国产一级在线免费观看| 香蕉A片| 免费观看色情视频| 日韩A片| 熟女中文字幕| 免费一二区| 东京热视频一区| 狠狠干干| 综合色婷婷一区二区亚洲欧美国产| 日韩AV成人电影| 爆操网站| 91美女在线视频| 狠狠艹狠狠干| 欧美足交视频| 日韩无修正| 插入综合网| 一区无码免费| 久久久久久三级电影| 亚洲日本黄色网址| 人人操在线| 亚洲欧美成人在线| 国产精品色哟哟| 欧美精品秘一区二区三区蜜臀| 欧美日韩在线一区| 婷婷伊人大香蕉| 超碰天天| 成人免费毛片蓝莓| 亚洲怡春院| 日本天天操| 超碰在线视| 蜜桃Av噜噜一区| 久久狼人| 久久这里只有精品9| 欧美熟妇一区二区三区| 国产成人中文字幕| 成年人视频免费| 欧美级黑寡妇毛片app| 婷婷五月激情中文字幕| 韩国午夜福利视频| 亚洲三级网站在线观看| 中文字幕的色| 五月天激情午夜福利| 国产AV自拍-久| 欧美日韩狠狠操在线观看视频 | 91高清视频| 日韩成人无码AV| 日韩成人无码一区二区| 久久99免费视频| 91丨九色丨东北熟女| h片免费在线观看| 人人操人人干人人摸| 97人人草| 国产V片| 在线黄片视频| 中文字幕亚洲在线观看| 不卡的av| 性色网| 国产熟女乱伦视频| 欧美视频在线观看免费| 黄色免费片| 久久精品福利视频| 激情六月婷婷| 国产黄色免费网站| 亚洲色图15P| 九色PORNY国产成人蝌蚪| 在线观看免费黄色视频| 国产视频福利| 亚洲福利视频电影精| 色高清无码免费视频| 美日韩在线观看| 精品人妻一区二区三区四区| 91在线无码精品国产三年| 怡春院综合| 很色很黄的A片一| 久久午夜无码鲁丝午夜精品| 一牛影视精品av| 人妻免费在线视频| 人妻丰满熟妇av无码| 亚洲欧美在线视频免费| 欧美国产操逼| 美日韩无码视频| 久久久免费观看视频| NP玩烂了公用爽灌满视频播放 | 日本免费a片| 夜夜夜影院| 国产精品三级在线观看| 国产欧美性爱| 99er热精品视频| 91香蕉在线| 黄色视频日本| 美女三片| 操屄视频播放| 日韩三级一区二区| 一区二区三区四区在线视频| 99操逼视频| 欧美日在线观看| 欧美成人无码一区二区三区| 一区二区三区网站| 日韩午夜电影| 九九热在线精品| 国精品伦一区一区三区有限公司 | 在线视频内射| 九九热视频在线| 最新中文字幕av| 成年人在线观看视频网站| 99热在线观看精品免费| 操老女人的逼| 久久99久久99久久99国内少妇精品| 99re视频在线观看| 国产欧美一区二区三区视频| 日韩AV无码网站| 精品人妻一区二区三区日产乱码 | 91精品日韩| 欧美日逼网| 日韩成人无码电影| 另类图片亚洲色图| 亚洲黄色成人网站| 亚洲色无码人妻激情| 欧美成人社区| 日韩精品人妻| 国产秘精品区二区三区日本| jizz日韩| eeuss一区二区| 三根一起进菊眼| 四虎黄色网| 日本在线观看| 强波多野结衣黑人| 亚洲人一级电影| 成人毛片一区二区三区无码| 可以免费观看的毛片| 九九综合伊人7777777| 国产三级午夜理伦三级| 日本少妇无码| 大鸡巴导航| 久久午夜无码鲁片午夜精品男男| 久久久久久国际四虎免费精品视频| 六月丁香久久| 中日韩黄色视频| 日韩色逼| 欧美日韩一级电影| 欧美一级网| 欧美亚韩一区二区三区| 国产系列每日更新| 国产精品一品二区三区的使用体验| 国产熟女AV| 日韩无码av电影| 俺去俺来WWW色官方| 日本超碰在线| 久久免费精品视频| 日韩人妻无码一区二区三区中文 | 欧美亚洲国产视频| 黄视频在线观看免费| 欧美一级黃色A片免费看小优视频| 中文字幕乱码人妻二区三区| 99色色| 高清毛片AAAAAAAAA片| 亚洲欧美日韩中文字幕在线观看| 国产美女在线播放| 少妇高潮喷水| 国产精品美女毛片真酒店| 久久久一区二区三区| 亚洲激情成人| 99re在线精品| 欧美老妇操逼视频| 日韩爱爱网| 91视频精品| 亚洲无码电影视频| 91人人妻人人妻人人澡| 久久久免费观看视频| 天天操夜| 午夜性爱网| 极品美女援交在线| aaa精品视频| 欧美熟女在线| 草b视频| 国产无遮挡又黄又爽免费网站| 狠狠干高清成人二区三区| 亚洲av综合在线| 嫩BBB槡BBBB槡BBBB百度| 中文字幕在线免费看线人| 欧美日视频| 国产黄色电影| 午夜一区二区三区免费| 国产视频入口| 色婷婷日韩精品一区二区三区| 国产黄色影院| 欧美乱轮| AV牛牛| 亚洲av网址| 黄网在线免费观看| 人人摸人人摸| 加勒比久久88| 青青草公开视频| 2019天天干| 日本中文字幕电影| 成人做爰100片免费看| 亚洲jiZZjiZZ日本少妇| 激情婷婷网| 91网址| 99中文字幕| 女公务员人妻呻吟求饶| 国产激情一区二区三区| 日本五十路熟女视频| 激情婷婷五月| 操逼基地| 精品无码一区二区三区在线| 亚洲日韩中文在线| 爱爱视频免费看| 亚洲无码电影视频| 69av网站| 特大妓女BBwBBWBBw| 国产在线第一页| 免费V片在线观看| 婷婷五月天丁香在线| 婷婷五月天网| 日韩精品无码一区二区| 蜜芽av在线观看| 操逼视频电影| 欧美一级免费观看| 超碰人人在线| 天天日bb| 久久无码一区二区| 无码中文字幕网站| 欧美精品久久久久久久久爆乳| 国产69AV| 久久久久亚洲AV无码麻豆| 国产午夜无码福利视频| 免费看特别黄色视频| 亚洲无码精品专区| 九九精品国产| 91就要爱爱视频| 一级真人毛片| 理论片91| 伊人成人在线观看| 亚洲中文字幕无码爆乳av| 国产精品久久久久久精| h片在线观看免费| 日韩A片免费| 男人操女人免费网站| 在线播放无码| 亚洲性爱在线| AV国产精品| 久操福利视频| 国产精品揄拍一区二区| 亚洲第一a| 精品无码视频在线观看| 无码一二三区| 少妇人妻一区| 俺来俺去www色官网| 免费毛片+一区二区三区| 中文无码久久| 伊大香蕉| 成人色色网| XXXX操| 久热免费视频在线观看| 欧美理论片在线观看| 午夜福利2025| 国产美女福利| 宅男视频| 无码免费中文字幕| 自拍超碰在线| 日本无码专区| 成人在线18禁| 操老女人逼视频| 国产综合av| 国产激倩都市一区二区三区欧美| 仓井空一区二区三区| 男女69视频| 成人精东影业JDAV3密友| 五月天福利影院| 黄色成人网站免费在线观看| 99青青草| 欧美日逼片| 91无码电影| 中国无码| 国产一区二区免费| 超碰免费97| 人人操人人网站| 操逼com| 欧美色色网| 亚洲AV无码久久久| 九色PORNY9l原创自拍| 久久伊人春色| 午夜探花在线观看| 人人草人人草| 91国产精品在线视频| 亚洲日韩免费观看|