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

MySQL 優(yōu)化原理,1W 字超詳細(xì)總結(jié)

共 15584字,需瀏覽 32分鐘

 ·

2020-11-19 23:14

??Java大聯(lián)盟

? 幫助萬千Java學(xué)習(xí)者持續(xù)成長

關(guān)注



作者|CHEN川


B 站搜索:楠哥教你學(xué)Java

獲取更多優(yōu)質(zhì)視頻教程

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

MySQL邏輯架構(gòu)

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


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


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

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

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

MySQL查詢過程

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

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

MySQL查詢過程

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

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

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

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

查詢緩存

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

MySQL將緩存存放在一個引用表(不要理解成table,可以認(rèn)為是類似于HashMap的數(shù)據(jù)結(jié)構(gòu)),通過一個哈希值索引,這個哈希值通過查詢本身、當(dāng)前要查詢的數(shù)據(jù)庫、客戶端協(xié)議版本號等一些可能影響結(jié)果的信息計算得來。所以兩個查詢在任何字符上的不同(例如:空格、注釋),都會導(dǎo)致緩存不會命中。

如果查詢中包含任何用戶自定義函數(shù)、存儲函數(shù)、用戶變量、臨時表、MySQL庫中的系統(tǒng)表,其查詢結(jié)果都不會被緩存。比如函數(shù)NOW()或者CURRENT_DATE()會因為不同的查詢時間,返回不同的查詢結(jié)果,再比如包含CURRENT_USER或者CONNECION_ID()的查詢語句會因為不同的用戶而返回不同的結(jié)果,將這樣的查詢結(jié)果緩存起來沒有任何的意義。

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

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

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

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

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

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

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

  4. 可以通過SQL_CACHE和SQL_NO_CACHE來控制某個查詢語句是否需要進行緩存

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

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

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

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

查詢優(yōu)化

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

MySQL使用基于成本的優(yōu)化器,它嘗試預(yù)測一個查詢使用某種執(zhí)行計劃時的成本,并選擇其中成本最小的一個。在MySQL可以通過查詢當(dāng)前會話的last_query_cost的值來得到其計算當(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個數(shù)據(jù)頁的隨機查找才能完成上面的查詢。這個結(jié)果是根據(jù)一些列的統(tǒng)計信息計算得來的,這些統(tǒng)計信息包括:每張表或者索引的頁面?zhèn)€數(shù)、索引的基數(shù)、索引和數(shù)據(jù)行的長度、索引的分布情況等等。

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

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

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

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

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

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

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

查詢執(zhí)行引擎

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

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

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

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

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

回頭總結(jié)一下MySQL整個查詢執(zhí)行過程,總的來說分為6個步驟:

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

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

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

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

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

性能優(yōu)化建議

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

二叉查找樹和平衡二叉樹

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

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

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

通過一次左旋操作就將插入后的樹重新變?yōu)槠胶舛鏄涫亲詈唵蔚那闆r了,實際應(yīng)用場景中可能需要旋轉(zhuǎn)多次。至此我們可以考慮一個問題,平衡二叉樹的查找效率還不錯,實現(xiàn)也非常簡單,相應(yīng)的維護成本還能接受,為什么MySQL索引不直接使用平衡二叉樹?

隨著數(shù)據(jù)庫中數(shù)據(jù)的增加,索引本身大小隨之增加,不可能全部存儲在內(nèi)存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產(chǎn)生磁盤I/O消耗,相對于內(nèi)存存取,I/O存取的消耗要高幾個數(shù)量級??梢韵胂笠幌乱豢脦装偃f節(jié)點的二叉樹的深度是多少?如果將這么大深度的一顆二叉樹放磁盤上,每讀取一個節(jié)點,需要一次磁盤的I/O讀取,整個查找的耗時顯然是不能夠接受的。那么如何減少查找過程中的I/O存取次數(shù)?

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

簡化B+Tree

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

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

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

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

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

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

leaf page和index page都沒有滿

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

Leaf Page拆分

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

Leaf Page與Index Page拆分

拆分后最終形成了這樣一顆樹。

最終樹

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

左旋操作

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

高性能策略

通過上文,相信你對B+Tree的數(shù)據(jù)結(jié)構(gòu)已經(jīng)有了大致的了解,但MySQL中索引是如何組織數(shù)據(jù)的存儲呢?以一個簡單的示例來說明,假如有如下數(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));

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

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

可以看到,索引首先根據(jù)第一個字段來排列順序,當(dāng)名字相同時,則根據(jù)第三個字段,即出生日期來排序,正是因為這個原因,才有了索引的“最左原則”。

1、MySQL不會使用索引的情況:非獨立的列

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

select * from where id + 1 = 5

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

2、前綴索引

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

3、多列索引和索引順序

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

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

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

select film_id,actor_id from film_actor where actor_id = 1 union allselect film_id,actor_id from film_actor where film_id = 1 and actor_id <> 1
  • 當(dāng)出現(xiàn)多個索引做相交操作時(多個AND條件),通常來說一個包含所有相關(guān)列的索引要優(yōu)于多個獨立索引。

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

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

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

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

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

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

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

select count(distinct staff_id)/count(*) as staff_id_selectivity,       count(distinct customer_id)/count(*) as customer_id_selectivity,       count(*) from payment

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

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

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

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

4、避免多個范圍條件

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

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

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

5、覆蓋索引

如果一個索引包含或者說覆蓋所有需要查詢的字段的值,那么就沒有必要再回表查詢,這就稱為覆蓋索引。覆蓋索引是非常有用的工具,可以極大的提高性能,因為查詢只需要掃描索引會帶來許多好處:

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

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

6、使用索引掃描來排序

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

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

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

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

// 最左列為常數(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)后立即刪除。比如有一個索引(A,B),再創(chuàng)建索引(A)就是冗余索引。冗余索引經(jīng)常發(fā)生在為表添加新索引時,比如有人新建了索引(A,B),但這個索引不是擴展已有的索引(A)。

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

8、刪除長期未使用的索引

定期刪除一些長時間未使用過的索引是一個非常好的習(xí)慣。

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

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

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

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

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

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

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

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

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

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

太抽象了?以上面的示例來說明,比如有這樣的一個查詢:

SELECT A.xx,B.yyFROM A INNER JOIN B USING(c)WHERE A.xx IN (5,6)

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

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列來查詢的,A.c上如果有索引的話,整個關(guān)聯(lián)查詢也不會使用。再看內(nèi)層的查詢,很明顯B.c上如果有索引的話,能夠加速查詢,因此只需要在關(guān)聯(lián)順序中的第二張表的相應(yīng)列上創(chuàng)建索引即可。

優(yōu)化LIMIT分頁

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

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

優(yōu)化這種查詢一個最簡單的辦法就是盡可能的使用覆蓋索引掃描,而不是查詢所有的列。然后根據(jù)需要做一次關(guān)聯(lián)查詢再返回所有的列。對于偏移量很大時,這樣做的效率會提升非常大??紤]下面的查詢:

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

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

SELECT film.film_id,film.descriptionFROM film INNER JOIN (    SELECT film_id FROM film ORDER BY title LIMIT 50,5) AS tmp USING(film_id);

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

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

SELECT id FROM t LIMIT 10000, 10;

改為:

SELECT id FROM t WHERE id > 10000 LIMIT 10;

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

優(yōu)化UNION

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

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

結(jié)語

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

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

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

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

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

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

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

    https://segmentfault.com/a/1190000004690721


推薦閱讀

1、Spring Boot+Vue項目實戰(zhàn)

2、B站:4小時上手MyBatis Plus

3、一文搞懂前后端分離

4、快速上手Spring Boot+Vue前后端分離


楠哥簡介

資深 Java 工程師,微信號?southwindss

《Java零基礎(chǔ)實戰(zhàn)》一書作者

騰訊課程官方 Java 面試官今日頭條認(rèn)證大V

GitChat認(rèn)證作者,B站認(rèn)證UP主(楠哥教你學(xué)Java)

致力于幫助萬千 Java 學(xué)習(xí)者持續(xù)成長。




有收獲,就在看?
瀏覽 36
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報
評論
圖片
表情
推薦
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報

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

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 中文字幕无码人妻在线视频| 国产小视频在线| 亚洲综合视频在线观看| 日韩av在线电影| www.豆花福利视频| 91丨精品丨国产丨丝袜| 自拍偷拍福利视频网站| 亚洲中文自拍| 色骚爽大香蕉91| 日韩无码影院| 激情动态视频| 特级西西444WWW视频| 亚洲精品秘一区二区三区在线观看| 中文字幕丰满的翔田千里| 成人黄色性视频| 猫咪视频大全视频| 99激情| 国产一卡二卡三卡| 中文字幕在线日亚洲9| 午夜成人精品| 91超碰大香蕉| 亚洲一区翔田千里无码| 91人人妻人人澡人人爽人人精品| 日夜夜操| 久久久久久成人无码| 久久精品电影| 亚洲综合自拍| 国产精品操逼视频| 西西4444www大胆无| 不卡视频一区二区| 可以免费观看的av| 亚洲婷婷网| 日本暖暖视频| 青青草91在线| 操逼视频试看| 黄色网址在线观看视频| 欧美成人一区二区三区片| 中文字幕观看av| 超碰中文字幕| 人人插人人| 欧美1区| a网站在线观看| 黄色一级在线观看| 欧美黄色录像| caopeng97| 欧美亚洲成人网| 欧美AAA片| 加勒比人妻| 91精品青青草| 亚洲国产精品精JIZZ老师| 91日韩视频在线| 五月亭亭在线视频| 水蜜桃视频网站在线观看| 新超碰在线观看| 操女人逼AV| 海滩AV黑人| 女生自慰在线观看| 国产一区二区精品| 国产成人a| av在线精品| 欧美9999| 一道本无吗一区| 国产av中文字幕| 亚洲成人电影一区| 亚洲综合中文字幕在线播放| 2018天天操天天干| 黄色网址五月天| av免费在线播放| 99一区二区| 亚洲免费小黄片| 欧美一级成人片| 国产中文字幕AV| 成人久久视频| 九色国产视频| 日本综合在线| 日产无码久久久久久| 翔田千里无码A片| 起碰视频| 久久成人久久爱| 精品成人Av一区二区三区| 99久久视频| 日韩在线视频网站| 91人人干| 国产成人无码精品| 日韩AV高清无码| 五月天婷婷国产| 久久99影院| 亚洲视频456| 99精品偷自拍| 五月天婷婷在线播放视频免费观看| 亚洲中文字幕在线观看免费 | 久久久久无码| 神马午夜精品| 亚洲操逼片| 国产激情精品| 国产成人精品免费视频| 综合网久久| 免费亚洲婷婷| 精品无码国产一区二区三区51安| 天堂在线最新资源| 91狠狠爱| 91久久精品国产91久久公交车 | 欧美精产国品一二三区别| 国产成人av在线观看| 成人片毛片| 中文字幕aV在线| 国产精品午夜福利视频| 国产精品夜夜爽7777777| 免费黄片网站在线观看| 亚洲日韩Av无码中文字幕美国 | 亚洲一区在线播放| 午夜激情视频在线观看| 日逼黄片| 女人的天堂AAA| 国产成人精品无码片区在线观91 | 中文字幕人妻无码| 四虎精品一区二区三区| 9I看片成人免费视频| 肉片无遮挡一区二区三区免费观看视频 | 免费在线观看内射| 中文字幕视频免费| 欧美A色| 不卡三区| 色色丁香| 成人做爰A片AAA毛真人| 久久久一区二区| 青草中文娱乐网在线| 欧美屄视频| 2020人妻中文字幕| 懂色AV无码中字幕一区| 少妇无码在线| 日本A在线观看| 中日韩免费视频| 一级a一级a爱片免费视频| 精品香蕉视频| 天天操夜夜操| 欧美久久电影| 337P大胆粉嫩银噜噜噜| www.91madou| 婷婷操逼网| 51国产黑料吃瓜在线入口| 综合国产| 国产精品欧美7777777| 无码熟妇| 搡BBBB搡BBB搡五十粉嫩| 91在线成人视频| 久久久精品在线| 天天躁夜夜躁av| 91AV视频在线| 无码国产视频| 水果派av解说| 国产激情都市一区二区三区欧美 | 国产免费av在线观看| 少妇搡BBBB搡BBB搡18禁| 欧美午夜激情视频| 69AV视频在线观看| AV资源在线播放| 操逼免费网站| 亚洲第一区欧美日韩| 女人久久久久| 欧美在线一级片| 玖玖av| 日韩精品91| 日韩黄色在线观看| 久久久久久久久久久久久自慰小片 | www伊人| 亚洲综合精品| 国产一级片在线播放| 99人妻视频| 奶大丰满一乱一视频一区二区三区在 | 精品一区二区三区视频| 91精品导航| 国产毛片在线| 久久亚洲Aⅴ成人无码国产丝袜| 操逼首页123| 婷婷高清无码| 豆花视频logo| 一级成人片| 国外成人在线视频老鸭窝| 欧美一级操逼视频| 少妇搡BBBB搡BBB搡HD(| 六月丁香视频| 自拍偷拍网站| 成人区色情综合小说| 少妇性受XXXX黑人XYX性爽| 久久天堂AV综合合色蜜桃网| 一区二区三区视频在线| 亚洲www啪成人一区二区麻豆| 色卻A| 天天夜夜有| 超碰护士| 婷婷视频网| 黄片国产| 久久久久久久久久久成人| 又紧又嫩又爽无遮挡免费| 91热视频| 婷婷黄色电影| 91亚洲一区| 无码欧美成人AAAA三区在线| 2025av天堂网| 亚洲天堂网在线观看| 一本久久精品一区二区| 黄在线免费观看| 成人黄色AV| 51妺嘿嘿午夜福利| 三级网站免费观看| 国产一级特黄A片| 亚洲乱伦视频| 大香蕉中文在线| 奇米超碰| 欧美aaa在线| jizzjizz欧美| a在线视频| 日本AA视频| 亚洲天堂网在线观看视频| 色热热| 在线国产视频| 玖玖中文字幕| 亚洲无码AV网站| 中文字幕AV播放| 亚洲日韩在线播放| 在线不卡中文字幕| 欧美老妇操逼视频| 东京热AV在线| 国产精品9999久久久久仙踪林| 黄色片AA| а√最新版在线中文8| 777777视频| 久久久久久成人无码| 久久精品导航| 亚洲无码视频在线看| 欧美日逼| 大屌在线| 国产中文在线观看| 97人妻精品| 蜜芽成人在线视频| 欧美性BBB槡BBB槡BBB| 成人黄色电影在线观看| 99热中文字幕在线观看| 骚逼逼影院| 蜜臀导航| 欧洲三级片网站| 国产精品久久久久久久9999| 日本黄在线观看| 国产精品天天AVJ精麻传媒| 国产乱子伦| 91在线无码精品秘入口国战| 色77777| 亚洲欧美日韩免费| 日一日射一射| 黑人一级片| 不卡无码免费视频| 亚欧成人网站| 免费毛片网站| 99国产高清| 熟女嗷嗷叫高潮合集91| 欧美三级在线播放| 99精品99| JiZZjiZZ亚洲成熟熟妇| 大香蕉av一区二区三区在线观看| 污视频网站免费观看| 99国产精品99久久久久久粉嫩| 国产三级视频| 蜜桃AV在线观看| 国产91精品在线观看| 高清无码一区| 天天草天天草| 久操免费在线视频| 中文字幕日本欧美| 3D动漫精品一区二区在线播放免费| 韩日一区二区| 亚洲免费在线看| 人人操人人干人人看| 少妇特黄A一区二区三区| 丁香花在线高清完整版视频| 极品一线天小嫩嫩真紧| 99激情| 在线操逼视频| 荫蒂添到高潮免费视频| 欧美性爱福利视频| 中文字幕成人| 夜夜嗨AⅤ一区二区三区| AV天堂无码| 天天射天天干| 丁香六月色| 无码AV电影| 91国黄色毛片在线观看| 国产又爽又黄视频在线看| 天天爽夜夜爽人人爽| 亚洲免费清高| 4438黄色| 亚洲清高毛无码毛片| 中文字幕亚洲日韩| 国产操屄网| 国产又爽又黄A片| 亚洲欧美日韩一区二区| 親子亂子倫XXXX| jizz久久| 色播五月婷婷| 国产激情内射| av无码免费观看| 丁香五月在线视频| 亚洲国产色婷婷| 新妺妺窝窝777777野外| 俺去操| 台湾中文字幕网| 国产一级A片久久久免费看快餐| 色aV牛牛在线观看| 超碰在线天天| 91成人免费| 亚洲vs无码蜜桃少妇| 亚洲免费观看高清完整版| 日本无码嫩草一区二区| www.蜜桃av| 日韩视频精品| 91免费观看国产| 欧美三p| 安徽妇搡BBB搡BBBB户外老太太| 日本三级久久| 欧美激情视频一区二区| 一区二区三区AV| 国产第七页| 欧美理论片在线观看| 久久动图| www.俺去| 一区二区三区精品| 韩国成人无码| 爱操视频| 狠狠操免费| 日皮视频免费在线观看| www五月天com| 国产一级婬乱片AV片AAA毛片| 国产精品一二三| 亚洲手机在线播放| 日韩五码| 国产成人三级在线播放| 无码AV电影| 天天操天天干麻豆| 欧美人人| 最新97色黄色精品高清网站| 色网站操逼| 噜噜视频| 黄色电影视频在线| 乱婬妺妺躁爽A片| 精品国产精品三级精品AV网址| 国产精彩视频| 精品人妻系列| 91精品国产aⅴ一区二区| 欲撸视频| 免费观看黄色视频网站| 天天干天天干| 一级午夜福利| 高清无码免费在线观看| 日本黄色视频在线免费观看| 91乱子伦国产乱子伦| 天天干天天肏| 久久久久网站| 欧美爱爱网站| 暗呦网一区二区三区| 91精品久久久久久综合五月天| 91人妻人人澡人人| 曰韩一级A片| 日本黄色一级| 91福利资源| 丁香婷婷视频| 老鸭窝久久| 一区二区三区无码免费| 97色在线视频| 黄色成人网站在线播放| 久久午夜无码鲁丝午夜精品| caopro| 无码视屏| 99黄色| 91AV免费看| 日韩综合另类| 久久久无码电影| 国产精品爽爽久久久久| 六月丁香综合| 国产1页| 久久久久亚洲AV无码专区成人 | 久久久久久久久国产精品| 麻豆AV电影| 有码一区二区三区| 亚洲成人福利电影| 91精品国产麻豆国产自产在线| 成人影片在线观看18| 日韩欧美高清第一期| 国产成人精品一区二区三区四区| www中文字幕| 人人爱久久| 中文在线字幕电视剧免费平台| 91密臀| 天天看天天射| 黑人一区二区三区四区| 成人网站在线免费| 亚洲最新无码| 精品无码一区二区| 日韩爱爱爱| 欧美成人性爱网址| 日韩在线精品视频| A视频免费| 亲子乱AⅤ一区二区三区| 狠狠狠狠狠狠操| 香蕉综合网| 精品码产区一区二亚洲国产| 99热9| 黄色电影视频网站| 久久免费看视频| av一区二区三区四区| 亚洲成人电影天堂| A片小视频| 91无码影院| 日韩A片一级无码免费蜜桃| 天堂一区二区18| 999成人电影| 免费看18禁| 日本免费在线观看视频| 亚洲日韩国产AV无码无码精品| 亚洲伦乱| 久久精品网| 人人爽久久涩噜噜噜网站| 永久免费不卡在线观看黄网站| 人人草人人操| 一卡二卡在线视频| 中文字幕久久无码| 97精品一区二区三区A片| www伦理片-韩国三级三级三级a三级-成人AV| 久久做爱视频| 东京热网站在线观看| 国产精品久久久久久久久久两年半| 操小嫩逼视频| 日本一级做a爱片| 激情综合婷婷久久| 日逼无码视频| 大香蕉69| 在线观看免费视频黄| 翔田千里被躁120分钟| AV在线一区二区| 9一区二区三区| www.欧美| 伊人大香蕉综合在线| 国产精品一级无码免费播放| 国产精品国产成人国产三级| 在线亚洲欧洲| 亚洲高清国产欧美综合s8| 99精品视频免费看| 亚洲人人18XXX—20HD| 国产在线激情| 日韩三级片无码| 国产AV无码影院| 最近中文字幕免费| 熟妇高潮| 中文字幕日韩无码片| 欧美日韩中文字幕在线| 狠狠色噜噜狠狠狠888米奇视频| 激情片AAA| 亚洲视频中文字幕在线观看| 超碰1999| 久久精品美臀| 一区二区三区精品视频| 羞羞av| 婷婷激情五月| 18禁网站禁片免费观看| 黄色视频网站在线播放| 色婷婷AV| 欧美特黄AAAAAAAAA片| 翔田千里一区二区三区精品播放 | 亚洲五月激情| 亚洲性爱在线观看| 一级成人毛片| 高清无码视频在线| 波多野结衣黄色| 国产一级二级在线观看| 日韩黄色片在线观看| 日韩在线不卡| AV无码电影| 国产一道本| 久久欧洲成人精品无码区| 国产成人无码永久免费| 秋霞福利影院| 黄色在线网| 免费观看av| 亚洲电影AV| 久热中文在线观看精品视频| 日韩在线免费| AV无码在线播放| 国产无码电影在线观看| 特黄AAAAAAAA片视频| 五月丁香大香蕉| 黄色三级av| 午夜AV福利影院| 精品无码人妻| 色超碰| 欧美一级黃色A片免费看蜜桃熟了| 韩国色情中文字幕| 午夜无码在线| 精品久久一区二区三区四区| 伊人三级网| 日本黄色免费视频| 在线天堂av| 欧美一区免费| 欧美黄色网视频| 国产做受91| 国产又爽又黄免费网站在线| 亚洲高清福利视频| 无码中文字幕高清| 国产免费AV在线| 玖玖av| 日韩色爱| 99热精品免费在线观看| 大香蕉性爱| av网站在线免费观看| 日本少妇BBw| 国产曰韩欧美综合另类在线 | 午夜激情国产| 天堂成人在线视频| 亚洲精品国产AV| 精品美女视频在线观看免费软件| 色老板综合| 免费人成年激情视频在线观看| 日本无码视频在线观看| 日本黄色录像| 狠狠狠狠狠狠狠狠狠狠| 中文字幕淫乱视频欧美| h片在线| 丁香五月天在线视频| 亚洲婷婷综合网| 欧美日韩AV| 黄色成人视频在线观看| 综合在线视频| 亚洲天堂手机在线| 亚洲AV无码成人精品区h麻豆 | 无码激情18激情视频| 激情AV在线观看| 亚洲综合电影| JLZZJLZZ亚洲女人| 在线视频你懂| 91嫩草久久久久久久| 人妻少妇av中文字幕乱码牛牛| 国产理论电影| 亚洲精品秘一区二区三小| 久久国色| 国产婷婷内射| 另类老妇性BBwBBw| 亲子乱AⅤ一区二区三区| 婷婷亚洲天堂| 青青草娱乐视频| 成人综合激情| 大香蕉伊人网| 日韩久久网| 精品国产欧美一区二区三区成人 | 被黑人猛躁10次高潮视频| 婷婷国产综合| 91网站观看| 午夜免费播放观看在线视频| 国产精品视频免费| 日韩另类视频| 欧美激情三区| 久久精品苍井空免费一区| 一级黄色影院| 中文字幕免费视频在线播放| 日韩精品人妻中文字幕蜜乳| WWWA级片| 午夜影音| 久久免费小视频| 五月丁香免费视频| 狠狠狠狠狠狠狠| 先锋影音中文字幕| 熟睡侵犯の奶水授乳在线| 久久久久久久97| 亚州天堂网| 欧美高清无码在线观看| 亚洲人妻在线播放| 粉嫩av懂色av蜜臀av熟妇| 超碰在线无码| 久草视频网| 国产嫩BBwBBw高潮| 日本A片在线播放| 国产在线视频一区| 911久久| 骚逼操| www.超碰在线| 亚洲AV成人片无码网站网蜜柚| 麻豆乱码国产一区二区三区| 日逼视频免费观看| 成人自拍偷拍视频| 亚洲成人影片| 日韩一级一片内射视频4K| 日本一级婬片A片免费看| 国产欧美在线观看不卡| 亚洲影院第一页在线观看| 欧美黄频| 国产精品女人777777| 北条麻妃91视频| 日本69视频| 色色综合视频| 又黄又爽无遮挡| 黄色毛片电影| 91爱爱视频| 婷婷五月天中文字幕| 操B网站| 一级毛AA片| 亚洲一级婬片A片AAAA网址| 热久久久久久| 午夜免费小视频| jizz无码| 国产日韩欧美在线播放| 久草超碰在线| 人妻视频在线| 97人人艹| 国产操比网| 国产精品视频你懂的| 五月一区| 人人看,人人摸| 操日本少妇| 亚洲欧美日韩电影| 亚洲福利片| 欧美婷婷| 性满足BBwBBWBBw| 激情五月天激情网| 色婷婷18正码国产| 狠狠躁日日躁夜夜躁2022麻豆| 欧美精品第一页| 久久国产精品网站| 久久机热| 黑人无码一二三四五区| 中文字幕特黄A片| 蜜臀AV一区二区| 成人aV无码精品国产一区二区| 亚洲成人无码一区| 国产精品无码专区| 亚洲中文字幕第一页| 激情久久五月天| 亲孑伦XXXⅹ熟女| 日本成人黄色电影| 日本爱爱免费| 五月丁香中文字幕| 99性爱视频| 91久久精品国产91久久公交车| 中国a一片一级一片| 国产精品揄拍100视频| 人人搞人人摸| 国产乱子伦真实精品| 中文字幕韩日| 北京熟妇搡BBBB搡BBBB电影 | www.av在线播放| 操美女视频网站| 欧美一級黃色A片免費看| 日韩免费视频在线观看| 婷婷成人小说| 丁香综合网| 国产毛片网| 国产高清视频在线| 国产伦子伦一级A片免费看老牛| 国产网友自拍| 91丨PORNY丨丰满人妻网站| 偷拍三区| 亚洲最新AV在线| 欧美黄色性爱| 亚洲黄色录像| 3级片网站| 久干妞| 免费视频一区二区三区四区| 亚洲AV无码A片在线观看蜜桃| 大香蕉伊人免费| 综合操逼网| av不卡在线| 97这里只有精品| 国产乱伦内射视频| 国产专区在线| 韩国三级HD中文字幕2019年| 91亚洲国产精品| 俺去俺来也www色官网cms| 国产精品内射| 亚洲Av无码午夜国产精品色软件 | 日韩高清无码一区二区三区| 超碰97免费| 亚洲成人电影AV| 国产视频高清无码| 加勒比无码高清| 偷拍92| 综合精品7799| 一区日韩| 日日骚中文字幕| 天天色天| 日韩人妻精品无码制服| 爱爱动态图| 又爽又黄免费网站97双女| 免费18禁网站| 亚洲性爱一区| 成人一级黄色电影| 日本翔田千里奶水| 小日本91在线观看| 99久久精品国产成人一区二区 | 中文字幕视频2023| 国产A片一区| 亚洲小黄片| 欧美色乱| 精品aaa| 欧美成人久久| 好看的中文字幕av| 中文字幕四区| 亚洲成人少妇老妇a视频在线| 亚洲群交视频| 午夜精品18视频国产17c| 蜜芽av在线观看| 毛片精品| 大乳奶一级婬片A片| 91无码影院| 91人妻人人操人人爽| 91N视频| 青草视频在线免费观看| 成人小说视频在线社区| 黄在线免费观看| 免费a视频| 少妇性受XXXX黑人XYX性爽| 九一九色国产| 中文字幕日韩无码片| 激情人妻AV| 亚洲AV无码一区二区三竹菊| 91久久人澡人妻人人做人人爽97| 日韩视频免费观看| 91爱搞在线| 日本爱爱免费视频| 亚洲精品天堂无码| 蜜臀av在线观看| 欧美日韩逼| 国产多人搡BBBB槡BBBB| 亚洲福利在线观看| 高清无码在线免费观看视频| 欧美精产国品一二三区| 国产亚洲99久久精品熟女| 欧美另类激情| 综合网操笔| 亚洲AV高清无码| 亚洲精品系列| 最新国产在线| 久久久精品999| 久久久久伊人| 五月婷婷俺也去| 口爆吞精在线观看| 成人中文字幕网站| 91精品国产偷窥一区二区| 久久久aaa| 国产精品永久| 久久五月天综合| 在线成人| 日韩在线三级片| 午夜精品视频| 中文字幕在线电影| 男人av网站| 激情精品| 亚洲无码手机在线| 91无码精品国产| 秋霞午夜福利影院| 亚洲在线看| www久久99| 网站色色免费看| 中文字幕第一| www.777熟女人妻| 国产伦乱| 日韩福利一区| 人人色人人色| 一级操逼毛片| 欧美一级片免费看| 成人福利网站| 亚洲av黄片| 在线免费看黄片| 免费成人视频| 99九九99九九九99九他書對| 91精品大屁股白浆自慰久久久 | 日韩黄色小电影| 伊人大香蕉视频| 操比在线| jizz在线视频| 中文字幕精品在线| 无码人妻A片一区二区青苹果| 北条麻妃无码在线视频| 玖玖爱国产| A免费在线观看| 国产特級黃色大片| 精品成人A片久久久久久不卡三区| 日韩第三页| 婷婷视频| 色综合天天综合网国产成人网| 日本日逼网| 中文字幕一区二区无码成人| 亚洲精品中文字幕乱码三区91| 强开小嫩苞毛片一二三区| 亚洲精品AⅤ一区二| 影音先锋国产av| 中文字幕亚洲观看| 国产视频中文字幕| 国产多人搡BBBB槡BBBB| 国产欧美一区二区三区特黄手机版| 天堂成人| 自拍偷拍综合网| 亚洲男女内射| 国产免费一区二区三区| 91久久精品一区二区三| 小黄片免费看| 日韩gay| 操逼观看| 99国产在线观看免费视频| 国产精品特级毛片| 国产乱轮视频| 亚韩AV| 中文字幕乱码视频| 中文字幕乱伦日本| 色婷婷AV国产精品| 中文字幕在线一区二区a| 人妻少妇中文字幕久久牛牛| 蜜臀久久99精品久久久电影| 国产黄色视频在线观看免费 | 精品无码一区二区三区四区久久久软件 | 高清无码在线视频| 日韩午夜成人电影| 大香蕉国产在线视频| 黄色免费毛片| 天天操夜夜操| 中文av在线播放| 97国产精品人人爽人人做| 日韩字幕无码| а√天堂中文最新版8| 91玖玖| 欧美日韩视频在线播放| 亚洲日韩在线中文字幕| 国产成人tv| 国产免费AV片在线无码免费看| www.人人操| 在线观看网址你懂的| 亚洲精品自拍偷拍| 北条麻妃精品青青久久价格| 综合网亚洲| 黄色成人在线免费观看| 日韩黄色片网站| 日韩AV大片| 女女女女女女BBBBBB手| 国产精品毛片| 色综合久久88色综合天天99| 日韩一区二区在线视频| 国产成人精品无码| 成人欧美一区二区三区在线观看| 天天操人人妻| 97无码视频| 大香蕉手机在线视频| 一本道视频在线| 中文字幕日本人妻| 免费观看黄片网站| 大香蕉第一页| 国产亚洲午夜久久久成人电影 | 天天干天| 亚洲中文字幕色| 亚洲最新AV网站| 澳门午夜黄色在线| 国产熟女一区二区三区五月婷| 伊人成人在线观看| 华女与黑人91A∨| 国产又爽又黄在线看| 日韩中文字幕网站| 亚洲午夜av| 91亚洲国产成人久久精品网站| 欧美精品久| 欧美三级免费| 午夜福利av在线| 日韩三级| 亚洲视屏| 精品二区| 免费一级黄色视频| a视频免费在线观看| 黄色A片免费看| 日韩中文字幕网站| 国产精品秘久久久久久一两个一起| 青娱乐精品在线视频| 91在线无码精品秘入口电车| 国产suv精品一区二区6精华液| 日皮视频网站| 操B五月天| 国产精品国内自产拍| 久久精品美臀| 亚洲欧美综合| 91日韩在线| 久久99视频| 免费AV在线| 色色婷婷五月天| 日韩中文在线观看| 91在线精品无码秘入口苹果 | 婷婷狠狠干| 老熟妇搡BBBB搡BBBB| 国产毛片久久久久久久| 日本韩国高清无码| 欧美色图另类图片| 夜夜嗨AV| 极品少妇av| 色婷婷在线综合| 青草视频网| 无码秘蜜桃吴梦梦| 国产人妖TS重口系列网站观看| 熟女在线视频| 超碰成人AV|