3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

关系数据库是如何工作的

發布時間:2025/3/21 数据库 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关系数据库是如何工作的 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一提到關系型數據庫,我禁不住想:有些東西被忽視了。關系型數據庫無處不在,而且種類繁多,從小巧實用的 SQLite 到強大的 Teradata 。但很少有文章講解數據庫是如何工作的。你可以自己谷歌/百度一下『關系型數據庫原理』,看看結果多么的稀少【譯者注:百度為您找到相關結果約1,850,000個…】?,而且找到的那些文章都很短。現在如果你查找最近時髦的技術(大數據、NoSQL或JavaScript),你能找到更多深入探討它們如何工作的文章。

難道關系型數據庫已經太古老太無趣,除了大學教材、研究文獻和書籍以外,沒人愿意講了嗎?

作為一個開發人員,我不喜歡用我不明白的東西。而且,數據庫已經使用了40年之久,一定有理由的。多年以來,我花了成百上千個小時來真正領會這些我每天都在用的、古怪的黑盒子。關系型數據庫非常有趣,因為它們是基于實用而且可復用的概念。如果你對了解一個數據庫感興趣,但是從未有時間或意愿來刻苦鉆研這個內容廣泛的課題,你應該喜歡這篇文章。

雖然本文標題很明確,但我的目的并不是講如何使用數據庫。因此,你應該已經掌握怎么寫一個簡單的 join query(聯接查詢)和CRUD操作(創建讀取更新刪除),否則你可能無法理解本文。這是唯一需要你了解的,其他的由我來講解。

我會從一些計算機科學方面的知識談起,比如時間復雜度。我知道有些人討厭這個概念,但是沒有它你就不能理解數據庫內部的巧妙之處。由于這是個很大的話題,我將集中探討我認為必要的內容:數據庫處理SQL查詢的方式。我僅僅介紹數據庫背后的基本概念,以便在讀完本文后你會對底層到底發生了什么有個很好的了解。

【譯者注:關于時間復雜度。計算機科學中,算法的時間復雜度是一個函數,它定量描述了該算法的運行時間。如果不了解這個概念建議先看看維基或百度百科,對于理解文章下面的內容很有幫助】

由于本文是個長篇技術文章,涉及到很多算法和數據結構知識,你盡可以慢慢讀。有些概念比較難懂,你可以跳過,不影響理解整體內容。

這篇文章大約分為3個部分:

  • 底層和上層數據庫組件概況
  • 查詢優化過程概況
  • 事務和緩沖池管理概況

回到基礎

很久很久以前(在一個遙遠而又遙遠的星系……),開發者必須確切地知道他們的代碼需要多少次運算。他們把算法和數據結構牢記于心,因為他們的計算機運行緩慢,無法承受對CPU和內存的浪費。

在這一部分,我將提醒大家一些這類的概念,因為它們對理解數據庫至關重要。我還會介紹數據庫索引的概念。

O(1) vs O(n^2)

現今很多開發者不關心時間復雜度……他們是對的。

但是當你應對大量的數據(我說的可不只是成千上萬哈)或者你要爭取毫秒級操作,那么理解這個概念就很關鍵了。而且你猜怎么著,數據庫要同時處理這兩種情景!我不會占用你太長時間,只要你能明白這一點就夠了。這個概念在下文會幫助我們理解什么是基于成本的優化。

概念

時間復雜度用來檢驗某個算法處理一定量的數據要花多長時間。為了描述這個復雜度,計算機科學家使用數學上的『簡明解釋算法中的大O符號』。這個表示法用一個函數來描述算法處理給定的數據需要多少次運算。

比如,當我說『這個算法是適用 O(某函數())』,我的意思是對于某些數據,這個算法需要 某函數(數據量) 次運算來完成。

重要的不是數據量,而是當數據量增加時運算如何增加。時間復雜度不會給出確切的運算次數,但是給出的是一種理念。

圖中可以看到不同類型的復雜度的演變過程,我用了對數尺來建這個圖。具體點兒說,數據量以很快的速度從1條增長到10億條。我們可得到如下結論:

  • 綠:O(1)或者叫常數階復雜度,保持為常數(要不人家就不會叫常數階復雜度了)。
  • 紅:O(log(n))對數階復雜度,即使在十億級數據量時也很低。
  • 粉:最糟糕的復雜度是 O(n^2),平方階復雜度,運算數快速膨脹。
  • 黑和藍:另外兩種復雜度(的運算數也是)快速增長。

例子

數據量低時,O(1) 和 O(n^2)的區別可以忽略不計。比如,你有個算法要處理2000條元素。

  • O(1) 算法會消耗 1 次運算
  • O(log(n)) 算法會消耗 7 次運算
  • O(n) 算法會消耗 2000 次運算
  • O(n*log(n)) 算法會消耗 14,000 次運算
  • O(n^2) 算法會消耗 4,000,000 次運算

O(1) 和 O(n^2) 的區別似乎很大(4百萬),但你最多損失 2 毫秒,只是一眨眼的功夫。確實,當今處理器每秒可處理上億次的運算。這就是為什么性能和優化在很多IT項目中不是問題。

我說過,面臨海量數據的時候,了解這個概念依然很重要。如果這一次算法需要處理 1,000,000 條元素(這對數據庫來說也不算大)。

  • O(1) 算法會消耗 1 次運算
  • O(log(n)) 算法會消耗 14 次運算
  • O(n) 算法會消耗 1,000,000 次運算
  • O(n*log(n)) 算法會消耗 14,000,000 次運算
  • O(n^2) 算法會消耗 1,000,000,000,000 次運算

我沒有具體算過,但我要說,用O(n^2) 算法的話你有時間喝杯咖啡(甚至再續一杯!)。如果在數據量后面加個0,那你就可以去睡大覺了。

繼續深入

為了讓你能明白

  • 搜索一個好的哈希表會得到 O(1) 復雜度
    • 搜索一個均衡的樹會得到 O(log(n)) 復雜度
    • 搜索一個陣列會得到 O(n) 復雜度
    • 最好的排序算法具有 O(n*log(n)) 復雜度
    • 糟糕的排序算法具有 O(n^2) 復雜度

注:在接下來的部分,我們將會研究這些算法和數據結構。

有多種類型的時間復雜度

  • 一般情況場景
  • 最佳情況場景
  • 最差情況場景

時間復雜度經常處于最差情況場景。

這里我只探討時間復雜度,但復雜度還包括:

  • 算法的內存消耗
  • 算法的磁盤 I/O 消耗

當然還有比 n^2 更糟糕的復雜度,比如:

  • n^4:差勁!我將要提到的一些算法具備這種復雜度。
  • 3^n:更差勁!本文中間部分研究的一些算法中有一個具備這種復雜度(而且在很多數據庫中還真的使用了)。
  • 階乘 n:你永遠得不到結果,即便在少量數據的情況下。
  • n^n:如果你發展到這種復雜度了,那你應該問問自己IT是不是你的菜。

注:我并沒有給出『大O表示法』的真正定義,只是利用這個概念。可以看看維基百科上的這篇文章。

合并排序

當你要對一個集合排序時你怎么做?什么?調用 sort() 函數……好吧,算你對了……但是對于數據庫,你需要理解這個 sort() 函數的工作原理。

優秀的排序算法有好幾個,我側重于最重要的一種:合并排序。你現在可能還不了解數據排序有什么用,但看完查詢優化部分后你就會知道了。再者,合并排序有助于我們以后理解數據庫常見的聯接操作,即合并聯接?。

合并

與很多有用的算法類似,合并排序基于這樣一個技巧:將 2 個大小為 N/2 的已排序序列合并為一個 N 元素已排序序列僅需要 N 次操作。這個方法叫做合并。

我們用個簡單的例子來看看這是什么意思:

通過此圖你可以看到,在 2 個 4元素序列里你只需要迭代一次,就能構建最終的8元素已排序序列,因為兩個4元素序列已經排好序了:

  • 1) 在兩個序列中,比較當前元素(當前=頭一次出現的第一個)
  • 2) 然后取出最小的元素放進8元素序列中
  • 3) 找到(兩個)序列的下一個元素,(比較后)取出最小的
  • 重復1、2、3步驟,直到其中一個序列中的最后一個元素
  • 然后取出另一個序列剩余的元素放入8元素序列中。

這個方法之所以有效,是因為兩個4元素序列都已經排好序,你不需要再『回到』序列中查找比較。

【譯者注:合并排序詳細原理,其中一個動圖(原圖較長,我做了刪減)清晰的演示了上述合并排序的過程,而原文的敘述似乎沒有這么清晰,不動戳大。】

既然我們明白了這個技巧,下面就是我的合并排序偽代碼。

C

1

2

3

4

5

6

7

8

9

10

11

12

13

array mergeSort(array a)

?? if(length(a)==1)

??????return a[0];

?? end if

?

?? //recursive calls

?? [left_array right_array] := split_into_2_equally_sized_arrays(a);

?? array new_left_array := mergeSort(left_array);

?? array new_right_array := mergeSort(right_array);

?

?? //merging the 2 small ordered arrays into a big one

?? array result := merge(new_left_array,new_right_array);

?? return result;

合并排序是把問題拆分為小問題,通過解決小問題來解決最初的問題(注:這種算法叫分治法,即『分而治之、各個擊破』)。如果你不懂,不用擔心,我第一次接觸時也不懂。如果能幫助你理解的話,我認為這個算法是個兩步算法:

  • 拆分階段,將序列分為更小的序列
  • 排序階段,把小的序列合在一起(使用合并算法)來構成更大的序列

拆分階段

在拆分階段過程中,使用3個步驟將序列分為一元序列。步驟數量的值是 log(N) (因為 N=8, log(N)=3)。【譯者注:底數為2,下文有說明】

我怎么知道這個的?

我是天才!一句話:數學。道理是每一步都把原序列的長度除以2,步驟數就是你能把原序列長度除以2的次數。這正好是對數的定義(在底數為2時)。

排序階段

在排序階段,你從一元序列開始。在每一個步驟中,你應用多次合并操作,成本一共是 N=8 次運算。

  • 第一步,4 次合并,每次成本是 2 次運算。
  • 第二步,2 次合并,每次成本是 4 次運算。
  • 第三步,1 次合并,成本是 8 次運算。

因為有 log(N) 個步驟,整體成本是 N*log(N) 次運算。

【譯者注:這個完整的動圖演示了拆分和排序的全過程,不動戳大。】

合并排序的強大之處

為什么這個算法如此強大?

因為:

  • 你可以更改算法,以便于節省內存空間,方法是不創建新的序列而是直接修改輸入序列。

注:這種算法叫『原地算法』(in-place algorithm)

  • 你可以更改算法,以便于同時使用磁盤空間和少量內存而避免巨量磁盤 I/O。方法是只向內存中加載當前處理的部分。在僅僅100MB的內存緩沖區內排序一個幾個GB的表時,這是個很重要的技巧。

注:這種算法叫『外部排序』(external sorting)。

  • 你可以更改算法,以便于在 多處理器/多線程/多服務器 上運行。

比如,分布式合并排序是Hadoop(那個著名的大數據框架)的關鍵組件之一。

  • 這個算法可以點石成金(事實如此!)

這個排序算法在大多數(如果不是全部的話)數據庫中使用,但是它并不是唯一算法。如果你想多了解一些,你可以看看?這篇論文,探討的是數據庫中常用排序算法的優勢和劣勢。

陣列,樹和哈希表

既然我們已經了解了時間復雜度和排序背后的理念,我必須要向你介紹3種數據結構了。這個很重要,因為它們是現代數據庫的支柱。我還會介紹數據庫索引的概念。

陣列

二維陣列是最簡單的數據結構。一個表可以看作是個陣列,比如:

這個二維陣列是帶有行與列的表:

  • 每個行代表一個主體
  • 列用來描述主體的特征
  • 每個列保存某一種類型對數據(整數、字符串、日期……)

雖然用這個方法保存和視覺化數據很棒,但是當你要查找特定的值它就很糟糕了。 舉個例子,如果你要找到所有在 UK 工作的人,你必須查看每一行以判斷該行是否屬于 UK 。這會造成 N 次運算的成本(N 等于行數),還不賴嘛,但是有沒有更快的方法呢?這時候樹就可以登場了(或開始起作用了)。

樹和數據庫索引

二叉查找樹是帶有特殊屬性的二叉樹,每個節點的關鍵字必須:

  • 比保存在左子樹的任何鍵值都要大
  • 比保存在右子樹的任何鍵值都要小

【譯者注:binary search tree,二叉查找樹/二叉搜索樹,或稱 Binary Sort Tree 二叉排序樹。見百度百科?】

概念

這個樹有 N=15 個元素。比方說我要找208:

  • 我從鍵值為 136 的根開始,因為 136<208,我去找節點136的右子樹。
  • 398>208,所以我去找節點398的左子樹
  • 250>208,所以我去找節點250的左子樹
  • 200<208,所以我去找節點200的右子樹。但是 200 沒有右子樹,值不存在(因為如果存在,它會在 200 的右子樹)

現在比方說我要找40

  • 我從鍵值為136的根開始,因為 136>40,所以我去找節點136的左子樹。
  • 80>40,所以我去找節點 80 的左子樹
  • 40=40,節點存在。我抽取出節點內部行的ID(圖中沒有畫)再去表中查找對應的 ROW ID。
  • 知道 ROW ID我就知道了數據在表中對精確位置,就可以立即獲取數據。

最后,兩次查詢的成本就是樹內部的層數。如果你仔細閱讀了合并排序的部分,你就應該明白一共有 log(N)層。所以這個查詢的成本是 log(N),不錯啊!

回到我們的問題

上文說的很抽象,我們回來看看我們的問題。這次不用傻傻的數字了,想象一下前表中代表某人的國家的字符串。假設你有個樹包含表中的列『country』:

  • 如果你想知道誰在 UK 工作
  • 你在樹中查找代表 UK 的節點
  • 在『UK 節點』你會找到 UK 員工那些行的位置

這次搜索只需 log(N) 次運算,而如果你直接使用陣列則需要 N 次運算。你剛剛想象的就是一個數據庫索引。

B+樹索引

查找一個特定值這個樹挺好用,但是當你需要查找兩個值之間的多個元素時,就會有大麻煩了。你的成本將是 O(N),因為你必須查找樹的每一個節點,以判斷它是否處于那 2 個值之間(例如,對樹使用中序遍歷)。而且這個操作不是磁盤I/O有利的,因為你必須讀取整個樹。我們需要找到高效的范圍查詢方法。為了解決這個問題,現代數據庫使用了一種修訂版的樹,叫做B+樹。在一個B+樹里:

  • 只有最底層的節點(葉子節點)才保存信息(相關表的行位置)
  • 其它節點只是在搜索中用來指引到正確節點的。

【譯者注:參考?B+樹?,?二叉樹遍歷????維基百科】

你可以看到,節點更多了(多了兩倍)。確實,你有了額外的節點,它們就是幫助你找到正確節點的『決策節點』(正確節點保存著相關表中行的位置)。但是搜索復雜度還是在 O(log(N))(只多了一層)。一個重要的不同點是,最底層的節點是跟后續節點相連接的。

用這個 B+樹,假設你要找40到100間的值:

  • 你只需要找 40(若40不存在則找40之后最貼近的值),就像你在上一個樹中所做的那樣。
  • 然后用那些連接來收集40的后續節點,直到找到100。

比方說你找到了 M 個后續節點,樹總共有 N 個節點。對指定節點的搜索成本是 log(N),跟上一個樹相同。但是當你找到這個節點,你得通過后續節點的連接得到 M 個后續節點,這需要 M 次運算。那么這次搜索只消耗了 M+log(N)?次運算,區別于上一個樹所用的 N 次運算。此外,你不需要讀取整個樹(僅需要讀 M+log(N) 個節點),這意味著更少的磁盤訪問。如果 M 很小(比如 200 行)并且 N 很大(1,000,000),那結果就是天壤之別了。

然而還有新的問題(又來了!)。如果你在數據庫中增加或刪除一行(從而在相關的 B+樹索引里):

  • 你必須在B+樹中的節點之間保持順序,否則節點會變得一團糟,你無法從中找到想要的節點。
  • 你必須盡可能降低B+樹的層數,否則 O(log(N)) 復雜度會變成 O(N)。

換句話說,B+樹需要自我整理和自我平衡。謝天謝地,我們有智能刪除和插入。但是這樣也帶來了成本:在B+樹中,插入和刪除操作是 O(log(N)) 復雜度。所以有些人聽到過使用太多索引不是個好主意這類說法。沒錯,你減慢了快速插入/更新/刪除表中的一個行的操作,因為數據庫需要以代價高昂的每索引 O(log(N)) 運算來更新表的索引。再者,增加索引意味著給事務管理器帶來更多的工作負荷(在本文結尾我們會探討這個管理器)。

想了解更多細節,你可以看看 Wikipedia 上這篇關于B+樹的文章。如果你想要數據庫中實現B+樹的例子,看看MySQL核心開發人員寫的這篇文章?和?這篇文章。兩篇文章都致力于探討 innoDB(mysql引擎)如何處理索引。

哈希表

我們最后一個重要的數據結構是哈希表。當你想快速查找值時,哈希表是非常有用的。而且,理解哈希表會幫助我們接下來理解一個數據庫常見的聯接操作,叫做『哈希聯接』。這個數據結構也被數據庫用來保存一些內部的東西(比如鎖表或者緩沖池,我們在下文會研究這兩個概念)。

哈希表這種數據結構可以用關鍵字來快速找到一個元素。為了構建一個哈希表,你需要定義:

  • 元素的關鍵字
    • 關鍵字的哈希函數。關鍵字計算出來的哈希值給出了元素的位置(叫做哈希桶)。
    • 關鍵字比較函數。一旦你找到正確的哈希桶,你必須用比較函數在桶內找到你要的元素。

一個簡單的例子

我們來看一個形象化的例子:

這個哈希表有10個哈希桶。因為我懶,我只給出5個桶,但是我知道你很聰明,所以我讓你想象其它的5個桶。我用的哈希函數是關鍵字對10取模,也就是我只保留元素關鍵字的最后一位,用來查找它的哈希桶:

  • 如果元素最后一位是 0,則進入哈希桶0,
  • 如果元素最后一位是 1,則進入哈希桶1,
  • 如果元素最后一位是 2,則進入哈希桶2,
  • …我用的比較函數只是判斷兩個整數是否相等。

【譯者注:取模運算】

比方說你要找元素 78:

  • 哈希表計算 78 的哈希碼,等于 8。
  • 查找哈希桶 8,找到的第一個元素是 78。
  • 返回元素 78。
  • 查詢僅耗費了 2 次運算(1次計算哈希值,另一次在哈希桶中查找元素)。

現在,比方說你要找元素 59:

  • 哈希表計算 59 的哈希碼,等于9。
  • 查找哈希桶 9,第一個找到的元素是 99。因為 99 不等于 59, 那么 99 不是正確的元素。
  • 用同樣的邏輯,查找第二個元素(9),第三個(79),……,最后一個(29)。
  • 元素不存在。
  • 搜索耗費了 7 次運算。

一個好的哈希函數

你可以看到,根據你查找的值,成本并不相同。

如果我把哈希函數改為關鍵字對 1,000,000 取模(就是說取后6位數字),第二次搜索只消耗一次運算,因為哈希桶 00059 里面沒有元素。真正的挑戰是找到好的哈希函數,讓哈希桶里包含非常少的元素。

在我的例子里,找到一個好的哈希函數很容易,但這是個簡單的例子。當關鍵字是下列形式時,好的哈希函數就更難找了:

  • 1 個字符串(比如一個人的姓)
  • 2 個字符串(比如一個人的姓和名)
  • 2 個字符串和一個日期(比如一個人的姓、名和出生年月日)

如果有了好的哈希函數,在哈希表里搜索的時間復雜度是 O(1)。

陣列 vs 哈希表

為什么不用陣列呢?

嗯,你問得好。

  • 一個哈希表可以只裝載一半到內存,剩下的哈希桶可以留在硬盤上。
  • 用陣列的話,你需要一個連續內存空間。如果你加載一個大表,很難分配足夠的連續內存空間。
  • 用哈希表的話,你可以選擇你要的關鍵字(比如,一個人的國家和姓氏)。

想要更詳細的信息,你可以閱讀我在Java HashMap?上的文章,是關于高效哈希表實現的。你不需要了解Java就能理解文章里的概念。

全局概覽

我們已經了解了數據庫內部的基本組件,現在我們需要回來看看數據庫的全貌了。

數據庫是一個易于訪問和修改的信息集合。不過簡單的一堆文件也能達到這個效果。事實上,像SQLite這樣最簡單的數據庫也只是一堆文件而已,但SQLite是精心設計的一堆文件,因為它允許你:

  • 使用事務來確保數據的安全和一致性
  • 快速處理百萬條以上的數據

數據庫一般可以用如下圖形來理解:

撰寫這部分之前,我讀過很多書/論文,它們都以自己的方式描述數據庫。所以,我不會特別關注如何組織數據庫或者如何命名各種進程,因為我選擇了自己的方式來描述這些概念以適應本文。區別就是不同的組件,總體思路為:數據庫是由多種互相交互的組件構成的。

核心組件:

  • 進程管理器(process manager):很多數據庫具備一個需要妥善管理的進程/線程池。再者,為了實現納秒級操作,一些現代數據庫使用自己的線程而不是操作系統線程。
  • 網絡管理器(network manager):網路I/O是個大問題,尤其是對于分布式數據庫。所以一些數據庫具備自己的網絡管理器。
  • 文件系統管理器(File system manager):磁盤I/O是數據庫的首要瓶頸。具備一個文件系統管理器來完美地處理OS文件系統甚至取代OS文件系統,是非常重要的。
  • 內存管理器(memory manager):為了避免磁盤I/O帶來的性能損失,需要大量的內存。但是如果你要處理大容量內存你需要高效的內存管理器,尤其是你有很多查詢同時使用內存的時候。
  • 安全管理器(Security Manager):用于對用戶的驗證和授權。
  • 客戶端管理器(Client manager):用于管理客戶端連接。
  • ……

工具:

  • 備份管理器(Backup manager):用于保存和恢復數據。
  • 復原管理器(Recovery manager):用于崩潰后重啟數據庫到一個一致狀態。
  • 監控管理器(Monitor manager):用于記錄數據庫活動信息和提供監控數據庫的工具。
  • Administration管理器(Administration?manager):用于保存元數據(比如表的名稱和結構),提供管理數據庫、模式、表空間的工具。【譯者注:好吧,我真的不知道Administration manager該翻譯成什么,有知道的麻煩告知,不勝感激……】
  • ……

查詢管理器:

  • 查詢解析器(Query parser):用于檢查查詢是否合法
  • 查詢重寫器(Query rewriter):用于預優化查詢
  • 查詢優化器(Query optimizer):用于優化查詢
  • 查詢執行器(Query executor):用于編譯和執行查詢

數據管理器:

  • 事務管理器(Transaction manager):用于處理事務
  • 緩存管理器(Cache manager):數據被使用之前置于內存,或者數據寫入磁盤之前置于內存
  • 數據訪問管理器(Data access manager):訪問磁盤中的數據

在本文剩余部分,我會集中探討數據庫如何通過如下進程管理SQL查詢的:

  • 客戶端管理器
  • 查詢管理器
  • 數據管理器(含復原管理器)

客戶端管理器

客戶端管理器是處理客戶端通信的。客戶端可以是一個(網站)服務器或者一個最終用戶或最終應用。客戶端管理器通過一系列知名的API(JDBC, ODBC, OLE-DB …)提供不同的方式來訪問數據庫。

客戶端管理器也提供專有的數據庫訪問API。

當你連接到數據庫時:

  • 管理器首先檢查你的驗證信息(用戶名和密碼),然后檢查你是否有訪問數據庫的授權。這些權限由DBA分配。
  • 然后,管理器檢查是否有空閑進程(或線程)來處理你對查詢。
  • 管理器還會檢查數據庫是否負載很重。
  • 管理器可能會等待一會兒來獲取需要的資源。如果等待時間達到超時時間,它會關閉連接并給出一個可讀的錯誤信息。
  • 然后管理器會把你的查詢送給查詢管理器來處理。
  • 因為查詢處理進程不是『不全則無』的,一旦它從查詢管理器得到數據,它會把部分結果保存到一個緩沖區并且開始給你發送。
  • 如果遇到問題,管理器關閉連接,向你發送可讀的解釋信息,然后釋放資源。

查詢管理器

這部分是數據庫的威力所在,在這部分里,一個寫得糟糕的查詢可以轉換成一個快速執行的代碼,代碼執行的結果被送到客戶端管理器。這個多步驟操作過程如下:

  • 查詢首先被解析并判斷是否合法
  • 然后被重寫,去除了無用的操作并且加入預優化部分
  • 接著被優化以便提升性能,并被轉換為可執行代碼和數據訪問計劃。
  • 然后計劃被編譯
  • 最后,被執行

這里我不會過多探討最后兩步,因為它們不太重要。

看完這部分后,如果你需要更深入的知識,我建議你閱讀:

  • 關于成本優化的初步研究論文(1979):關系型數據庫系統存取路徑選擇。這個篇文章只有12頁,而且具備計算機一般水平就能理解。
  • 非常好、非常深入的?DB2 9.X 如何優化查詢的介紹
  • 非常好的PostgreSQL如何優化查詢的介紹。這是一篇最通俗易懂的文檔,因為它講的是『我們來看看在這種情況下,PostgreSQL給出了什么樣的查詢計劃』,而不是『我們來看看PostgreSQL用的什么算法』。
  • 官方SQLite優化文檔。『易于』閱讀,因為SQLite用的是簡單規則。再者,這是唯一真正解釋SQLite如何工作的官方文檔。
  • 非常好的SQL Server 2005 如何優化查詢的介紹
  • Oracle 12c 優化白皮書
  • 2篇查詢優化的教程,第一篇?第二篇。教程來自《數據庫系統概念》的作者,很好的讀物,集中討論磁盤I/O,但是要求具有很好的計算機科學水平。
  • 另一個原理教程,這篇教程我覺得更易懂,不過它僅關注聯接運算符(join operators)和磁盤I/O。

查詢解析器

每一條SQL語句都要送到解析器來檢查語法,如果你的查詢有錯,解析器將拒絕該查詢。比如,如果你寫成”SLECT …” 而不是 “SELECT …”,那就沒有下文了。

但這還不算完,解析器還會檢查關鍵字是否使用正確的順序,比如 WHERE 寫在 SELECT 之前會被拒絕。

然后,解析器要分析查詢中的表和字段,使用數據庫元數據來檢查:

  • 表是否存在
  • 表的字段是否存在
  • 對某類型字段的?運算?是否?可能(比如,你不能將整數和字符串進行比較,你不能對一個整數使用 substring() 函數)

接著,解析器檢查在查詢中你是否有權限來讀取(或寫入)表。再強調一次:這些權限由DBA分配。

在解析過程中,SQL 查詢被轉換為內部表示(通常是一個樹)。

如果一切正常,內部表示被送到查詢重寫器。

查詢重寫器

在這一步,我們已經有了查詢的內部表示,重寫器的目標是:

  • 預優化查詢
  • 避免不必要的運算
  • 幫助優化器找到合理的最佳解決方案

重寫器按照一系列已知的規則對查詢執行檢測。如果查詢匹配一種模式的規則,查詢就會按照這條規則來重寫。下面是(可選)規則的非詳盡的列表:

  • 視圖合并:如果你在查詢中使用視圖,視圖就會轉換為它的 SQL 代碼。
  • 子查詢扁平化:子查詢是很難優化的,因此重寫器會嘗試移除子查詢

例如:

MySQL

1

2

3

4

5

6

SELECTPERSON.*

FROMPERSON

WHEREPERSON.person_keyIN

(SELECTMAILS.person_key

FROMMAILS

WHEREMAILS.mailLIKE'christophe%');

會轉換為:

MySQL

1

2

3

4

SELECT PERSON.*

FROM PERSON, MAILS

WHERE PERSON.person_key = MAILS.person_key

and MAILS.mail LIKE 'christophe%';

?

  • 去除不必要的運算符:比如,如果你用了 DISTINCT,而其實你有 UNIQUE 約束(這本身就防止了數據出現重復),那么 DISTINCT 關鍵字就被去掉了。
  • 排除冗余的聯接:如果相同的 JOIN 條件出現兩次,比如隱藏在視圖中的 JOIN 條件,或者由于傳遞性產生的無用 JOIN,都會被消除。
  • 常數計算賦值:如果你的查詢需要計算,那么在重寫過程中計算會執行一次。比如 WHERE AGE > 10+2 會轉換為 WHERE AGE > 12 , TODATE(“日期字符串”) 會轉換為 datetime 格式的日期值。
  • (高級)分區裁剪(Partition Pruning):如果你用了分區表,重寫器能夠找到需要使用的分區。
  • (高級)物化視圖重寫(Materialized view rewrite):如果你有個物化視圖匹配查詢謂詞的一個子集,重寫器將檢查視圖是否最新并修改查詢,令查詢使用物化視圖而不是原始表。
  • (高級)自定義規則:如果你有自定義規則來修改查詢(就像 Oracle policy),重寫器就會執行這些規則。
  • (高級)OLAP轉換:分析/加窗 函數,星形聯接,ROLLUP 函數……都會發生轉換(但我不確定這是由重寫器還是優化器來完成,因為兩個進程聯系很緊,必須看是什么數據庫)。

【譯者注:?物化視圖? 。謂詞,predicate,條件表達式的求值返回真或假的過程】

重寫后的查詢接著送到優化器,這時候好玩的就開始了。

統計

研究數據庫如何優化查詢之前我們需要談談統計,因為沒有統計的數據庫是愚蠢的。除非你明確指示,數據庫是不會分析自己的數據的。沒有分析會導致數據庫做出(非常)糟糕的假設。

但是,數據庫需要什么類型的信息呢?

我必須(簡要地)談談數據庫和操作系統如何保存數據。兩者使用的最小單位叫做頁或塊(默認 4 或 8 KB)。這就是說如果你僅需要 1KB,也會占用一個頁。要是頁的大小為 8KB,你就浪費了 7KB。

回來繼續講統計!?當你要求數據庫收集統計信息,數據庫會計算下列值:

  • 表中行和頁的數量
  • 表中每個列中的:
    唯一值
    數據長度(最小,最大,平均)
    數據范圍(最小,最大,平均)
  • 表的索引信息

這些統計信息會幫助優化器估計查詢所需的磁盤 I/O、CPU、和內存使用

對每個列的統計非常重要。
比如,如果一個表 PERSON 需要聯接 2 個列: LAST_NAME, FIRST_NAME。
根據統計信息,數據庫知道FIRST_NAME只有 1,000 個不同的值,LAST_NAME 有 1,000,000 個不同的值。
因此,數據庫就會按照 LAST_NAME, FIRST_NAME 聯接。
因為 LAST_NAME 不大可能重復,多數情況下比較 LAST_NAME 的頭 2 、 3 個字符就夠了,這將大大減少比較的次數。

不過,這些只是基本的統計。你可以讓數據庫做一種高級統計,叫直方圖。直方圖是列值分布情況的統計信息。例如:

  • 出現最頻繁的值
  • 分位數?【譯者注:http://baike.baidu.com/view/1323572.htm】

這些額外的統計會幫助數據庫找到更佳的查詢計劃,尤其是對于等式謂詞(例如: WHERE AGE = 18 )或范圍謂詞(例如: WHERE AGE > 10 and AGE < 40),因為數據庫可以更好的了解這些謂詞相關的數字類型數據行(注:這個概念的技術名稱叫選擇率)。

統計信息保存在數據庫元數據內,例如(非分區)表的統計信息位置:

  • Oracle: USER / ALL / DBA_TABLES 和 USER / ALL / DBA_TAB_COLUMNS
  • DB2: SYSCAT.TABLES 和 SYSCAT.COLUMNS

統計信息必須及時更新。如果一個表有 1,000,000 行而數據庫認為它只有 500 行,沒有比這更糟糕的了。統計唯一的不利之處是需要時間來計算,這就是為什么數據庫大多默認情況下不會自動計算統計信息。數據達到百萬級時統計會變得困難,這時候,你可以選擇僅做基本統計或者在一個數據庫樣本上執行統計。

舉個例子,我參與的一個項目需要處理每表上億條數據的庫,我選擇只統計10%,結果造成了巨大的時間消耗。本例證明這是個糟糕的決定,因為有時候?Oracle?10G 從特定表的特定列中選出的 10% 跟全部 100% 有很大不同(對于擁有一億行數據的表,這種情況極少發生)。這次錯誤的統計導致了一個本應 30 秒完成的查詢最后執行了 8 個小時,查找這個現象根源的過程簡直是個噩夢。這個例子顯示了統計的重要性。

注:當然了,每個數據庫還有其特定的更高級的統計。如果你想了解更多信息,讀讀數據庫的文檔。話雖然這么說,我已經盡力理解統計是如何使用的了,而且我找到的最好的官方文檔來自PostgreSQL。

查詢優化器

所有的現代數據庫都在用基于成本的優化(即CBO)來優化查詢。道理是針對每個運算設置一個成本,通過應用成本最低廉的一系列運算,來找到最佳的降低查詢成本的方法。

為了理解成本優化器的原理,我覺得最好用個例子來『感受』一下這個任務背后的復雜性。這里我將給出聯接 2 個表的 3 個方法,我們很快就能看到即便一個簡單的聯接查詢對于優化器來說都是個噩夢。之后,我們會了解真正的優化器是怎么做的。

對于這些聯接操作,我會專注于它們的時間復雜度,但是,數據庫優化器計算的是它們的 CPU 成本、磁盤 I/O 成本、和內存需求。時間復雜度和 CPU 成本的區別是,時間成本是個近似值(給我這樣的懶家伙準備的)。而 CPU 成本,我這里包括了所有的運算,比如:加法、條件判斷、乘法、迭代……還有呢:

  • 每一個高級代碼運算都要特定數量的低級 CPU 運算。
  • 對于 Intel Core i7、Intel Pentium 4、AMD Opteron…等,(就 CPU 周期而言)CPU 的運算成本是不同的,也就是說它取決于 CPU 的架構。

使用時間復雜度就容易多了(至少對我來說),用它我也能了解到 CBO 的概念。由于磁盤 I/O 是個重要的概念,我偶爾也會提到它。請牢記,大多數時候瓶頸在于磁盤 I/O 而不是 CPU 使用。

索引

在研究 B+樹的時候我們談到了索引,要記住一點,索引都是已經排了序的。

僅供參考:還有其他類型的索引,比如位圖索引,在 CPU、磁盤I/O、和內存方面與B+樹索引的成本并不相同。

另外,很多現代數據庫為了改善執行計劃的成本,可以僅為當前查詢動態地生成臨時索引。

存取路徑

在應用聯接運算符(join operators)之前,你首先需要獲得數據。以下就是獲得數據的方法。

注:由于所有存取路徑的真正問題是磁盤 I/O,我不會過多探討時間復雜度。

【譯者注:四種類型的Oracle索引掃描介紹? 】

全掃描

如果你讀過執行計劃,一定看到過『全掃描』(或只是『掃描』)一詞。簡單的說全掃描就是數據庫完整的讀一個表或索引。就磁盤 I/O 而言,很明顯全表掃描的成本比索引全掃描要高昂。

范圍掃描

其他類型的掃描有索引范圍掃描,比如當你使用謂詞 ” WHERE AGE > 20 AND AGE < 40 ” 的時候它就會發生。

當然,你需要在 AGE 字段上有索引才能用到索引范圍掃描。

在第一部分我們已經知道,范圍查詢的時間成本大約是 log(N)+M,這里 N 是索引的數據量,M 是范圍內估測的行數。多虧有了統計我們才能知道 N 和 M 的值(注: M 是謂詞 “ AGE > 20 AND AGE < 40 ” 的選擇率)。另外范圍掃描時,你不需要讀取整個索引,因此在磁盤 I/O 方面沒有全掃描那么昂貴。

唯一掃描

如果你只需要從索引中取一個值你可以用唯一掃描。

根據 ROW ID 存取

多數情況下,如果數據庫使用索引,它就必須查找與索引相關的行,這樣就會用到根據 ROW ID 存取的方式。

例如,假如你運行:

MySQL

1

SELECTLASTNAME,FIRSTNAMEfromPERSONWHEREAGE=28

如果 person 表的 age 列有索引,優化器會使用索引找到所有年齡為 28 的人,然后它會去表中讀取相關的行,這是因為索引中只有 age 的信息而你要的是姓和名。

但是,假如你換個做法:

MySQL

1

2

SELECT TYPE_PERSON.CATEGORY from PERSON ,TYPE_PERSON

WHERE PERSON.AGE = TYPE_PERSON.AGE

PERSON 表的索引會用來聯接 TYPE_PERSON 表,但是 PERSON 表不會根據行ID 存取,因為你并沒有要求這個表內的信息。

雖然這個方法在少量存取時表現很好,這個運算的真正問題其實是磁盤 I/O。假如需要大量的根據行ID存取,數據庫也許會選擇全掃描。

其它路徑

我沒有列舉所有的存取路徑,如果你感興趣可以讀一讀?Oracle文檔。其它數據庫里也許叫法不同但背后的概念是一樣的。

聯接運算符

那么,我們知道如何獲取數據了,那現在就把它們聯接起來!

我要展現的是3個個常用聯接運算符:合并聯接(Merge join),哈希聯接(Hash Join)和嵌套循環聯接(Nested Loop Join)。但是在此之前,我需要引入新詞匯了:內關系和外關系( inner relation and outer relation)?【譯者注: “內關系和外關系” 這個說法來源不明,跟查詢的“內聯接(INNER JOIN)??、外聯接(OUTER JOIN)? ” 不是一個概念 。只查到百度百科詞條:關系數據庫?里提到“每個表格(有時被稱為一個關系)……” 。 其他參考鏈接 “Merge Join”?? “Hash Join”?? “Nested Loop Join”?】??。?一個關系可以是:

  • 一個表
  • 一個索引
  • 上一個運算的中間結果(比如上一個聯接運算的結果)

當你聯接兩個關系時,聯接算法對兩個關系的處理是不同的。在本文剩余部分,我將假定:

  • 外關系是左側數據集
  • 內關系是右側數據集

比如, A JOIN B 是 A 和 B 的聯接,這里 A 是外關系,B 是內關系。

多數情況下,?A JOIN B 的成本跟 B JOIN A 的成本是不同的。

在這一部分,我還將假定外關系有 N 個元素,內關系有 M 個元素。要記住,真實的優化器通過統計知道 N 和 M 的值。

注:N 和 M 是關系的基數。【譯者注:?基數?】

嵌套循環聯接

嵌套循環聯接是最簡單的。

道理如下:

  • 針對外關系的每一行
  • 查看內關系里的所有行來尋找匹配的行

下面是偽代碼:

C

1

2

3

4

5

6

7

8

nested_loop_join(arrayouter,arrayinner)

??foreachrowainouter

????foreachrowbininner

??????if(match_join_condition(a,b))

????????write_result_in_output(a,b)

??????endif

????endfor

??endfor

由于這是個雙迭代,時間復雜度是 O(N*M)。

在磁盤 I/O 方面, 針對 N 行外關系的每一行,內部循環需要從內關系讀取 M 行。這個算法需要從磁盤讀取 N+ N*M 行。但是,如果內關系足夠小,你可以把它讀入內存,那么就只剩下 M + N 次讀取。這樣修改之后,內關系必須是最小的,因為它有更大機會裝入內存。

在CPU成本方面沒有什么區別,但是在磁盤 I/O 方面,最好最好的,是每個關系只讀取一次。

當然,內關系可以由索引代替,對磁盤 I/O 更有利。

由于這個算法非常簡單,下面這個版本在內關系太大無法裝入內存時,對磁盤 I/O 更加有利。道理如下:

  • 為了避免逐行讀取兩個關系,
  • 你可以成簇讀取,把(兩個關系里讀到的)兩簇數據行保存在內存里,
  • 比較兩簇數據,保留匹配的,
  • 然后從磁盤加載新的數據簇來繼續比較
  • 直到加載了所有數據。

可能的算法如下:

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// improved version to reduce the disk I/O.

nested_loop_join_v2(file outer, file inner)

??for each bunch ba in outer

??// ba is now in memory

????for each bunch bb in inner

????????// bb is now in memory

????????for each row a in ba

??????????for each row b in bb

????????????if (match_join_condition(a,b))

??????????????write_result_in_output(a,b)

????????????end if

??????????end for

?????? end for

????end for

?? end for

使用這個版本,時間復雜度沒有變化,但是磁盤訪問降低了:

  • 用前一個版本,算法需要 N + N*M 次訪問(每次訪問讀取一行)。
  • 用新版本,磁盤訪問變為?外關系的數據簇數量 + 外關系的數據簇數量 * 內關系的數據簇數量。
  • 增加數據簇的尺寸,可以降低磁盤訪問。

哈希聯接

哈希聯接更復雜,不過在很多場合比嵌套循環聯接成本低。

哈希聯接的道理是:

  • 1) 讀取內關系的所有元素
  • 2) 在內存里建一個哈希表
  • 3) 逐條讀取外關系的所有元素
  • 4) (用哈希表的哈希函數)計算每個元素的哈希值,來查找內關系里相關的哈希桶內
  • 5) 是否與外關系的元素匹配。

在時間復雜度方面我需要做些假設來簡化問題:

  • 內關系被劃分成 X 個哈希桶
  • 哈希函數幾乎均勻地分布每個關系內數據的哈希值,就是說哈希桶大小一致。
  • 外關系的元素與哈希桶內的所有元素的匹配,成本是哈希桶內元素的數量。

時間復雜度是 (M/X) * (N/X) + 創建哈希表的成本(M) + 哈希函數的成本 * N 。
如果哈希函數創建了足夠小規模的哈希桶,那么復雜度就是 O(M+N)。

還有個哈希聯接的版本,對內存有利但是對磁盤 I/O 不夠有利。 這回是這樣的:

  • 1) 計算內關系和外關系雙方的哈希表
  • 2) 保存哈希表到磁盤
  • 3) 然后逐個哈希桶比較(其中一個讀入內存,另一個逐行讀取)。

合并聯接

合并聯接是唯一產生排序的聯接算法。

注:這個簡化的合并聯接不區分內表或外表;兩個表扮演同樣的角色。但是真實的實現方式是不同的,比如當處理重復值時。

1.(可選)排序聯接運算:兩個輸入源都按照聯接關鍵字排序。

2.合并聯接運算:排序后的輸入源合并到一起。

排序

我們已經談到過合并排序,在這里合并排序是個很好的算法(但是并非最好的,如果內存足夠用的話,還是哈希聯接更好)。

然而有時數據集已經排序了,比如:

  • 如果表內部就是有序的,比如聯接條件里一個索引組織表?【譯者注:?index-organized table?】
  • 如果關系是聯接條件里的一個索引
  • 如果聯接應用在一個查詢中已經排序的中間結果

合并聯接

這部分與我們研究過的合并排序中的合并運算非常相似。不過這一次呢,我們不是從兩個關系里挑選所有元素,而是只挑選相同的元素。道理如下:

  • 1) 在兩個關系中,比較當前元素(當前=頭一次出現的第一個)
  • 2) 如果相同,就把兩個元素都放入結果,再比較兩個關系里的下一個元素
  • 3) 如果不同,就去帶有最小元素的關系里找下一個元素(因為下一個元素可能會匹配)
  • 4) 重復 1、2、3步驟直到其中一個關系的最后一個元素。

因為兩個關系都是已排序的,你不需要『回頭去找』,所以這個方法是有效的。

該算法是個簡化版,因為它沒有處理兩個序列中相同數據出現多次的情況(即多重匹配)。真實版本『僅僅』針對本例就更加復雜,所以我才選擇簡化版。

如果兩個關系都已經排序,時間復雜度是 O(N+M)

如果兩個關系需要排序,時間復雜度是對兩個關系排序的成本:O(N*Log(N) + M*Log(M))

對于計算機極客,我給出下面這個可能的算法來處理多重匹配(注:對于這個算法我不保證100%正確):

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

mergeJoin(relationa,relationb)

??relationoutput

??integera_key:=0;

??integer??b_key:=0;

?

??while(a[a_key]!=nullandb[b_key]!=null)

????if(a[a_key]<b[b_key])

??????a_key++;

????elseif(a[a_key]>b[b_key])

??????b_key++;

????else//Join predicate satisfied

??????write_result_in_output(a[a_key],b[b_key])

??????//We need to be careful when we increase the pointers

??????if(a[a_key+1]!=b[b_key])

????????b_key++;

??????endif

??????if(b[b_key+1]!=a[a_key])

????????a_key++;

??????endif

??????if(b[b_key+1]==a[a_key]&&b[b_key]==a[a_key+1])

????????b_key++;

????????a_key++;

??????endif

????endif

??endwhile

?

哪個算法最好?

如果有最好的,就沒必要弄那么多種類型了。這個問題很難,因為很多因素都要考慮,比如:

  • 空閑內存:沒有足夠的內存的話就跟強大的哈希聯接拜拜吧(至少是完全內存中哈希聯接)。
  • 兩個數據集的大小。比如,如果一個大表聯接一個很小的表,那么嵌套循環聯接就比哈希聯接快,因為后者有創建哈希的高昂成本;如果兩個表都非常大,那么嵌套循環聯接CPU成本就很高昂。
  • 是否有索引:有兩個 B+樹索引的話,聰明的選擇似乎是合并聯接。
  • 結果是否需要排序:即使你用到的是未排序的數據集,你也可能想用成本較高的合并聯接(帶排序的),因為最終得到排序的結果后,你可以把它和另一個合并聯接串起來(或者也許因為查詢用 ORDER BY/GROUP BY/DISTINCT 等操作符隱式或顯式地要求一個排序結果)。
  • 關系是否已經排序:這時候合并聯接是最好的候選項。
  • 聯接的類型:是等值聯接(比如 tableA.col1 = tableB.col2 )? 還是內聯接?外聯接?笛卡爾乘積?或者自聯接?有些聯接在特定環境下是無法工作的。
  • 數據的分布:如果聯接條件的數據是傾斜的(比如根據姓氏來聯接人,但是很多人同姓),用哈希聯接將是個災難,原因是哈希函數將產生分布極不均勻的哈希桶。
  • 如果你希望聯接操作使用多線程或多進程。

想要更詳細的信息,可以閱讀DB2,?ORACLE?或?SQL Server)的文檔。

簡化的例子

我們已經研究了 3 種類型的聯接操作。

現在,比如說我們要聯接 5 個表,來獲得一個人的全部信息。一個人可以有:

  • 多個手機號(MOBILES)
  • 多個郵箱(MAILS)
  • 多個地址(ADRESSES)
  • 多個銀行賬號(BANK_ACCOUNTS)

換句話說,我們需要用下面的查詢快速得到答案:

MySQL

1

2

3

4

5

6

SELECT * from PERSON, MOBILES, MAILS,ADRESSES, BANK_ACCOUNTS

WHERE

PERSON.PERSON_ID = MOBILES.PERSON_ID

AND PERSON.PERSON_ID = MAILS.PERSON_ID

AND PERSON.PERSON_ID = ADRESSES.PERSON_ID

AND PERSON.PERSON_ID = BANK_ACCOUNTS.PERSON_ID

作為一個查詢優化器,我必須找到處理數據最好的方法。但有 2 個問題:

  • 每個聯接使用那種類型?
    我有 3 種可選(哈希、合并、嵌套),同時可能用到 0, 1 或 2 個索引(不必說還有多種類型的索引)。
  • 按什么順序執行聯接?
    比如,下圖顯示了針對 4 個表僅僅 3 次聯接,可能采用的執行計劃:

那么下面就是我可能采取的方法:

  • 1) 采取粗暴的方式
    用數據庫統計,計算每種可能的執行計劃的成本,保留最佳方案。但是,會有很多可能性。對于一個給定順序的聯接操作,每個聯接有三種可能性:哈希、合并、嵌套,那么總共就有 3^4 種可能性。確定聯接的順序是個二叉樹的排列問題,會有?(2*4)!/(4+1)! 種可能的順序。對本例這個相當簡化了的問題,我最后會得到 3^4*(2*4)!/(4+1)! 種可能。
    拋開專業術語,那相當于 27,216 種可能性。如果給合并聯接加上使用 0,1 或 2 個 B+樹索引,可能性就變成了 210,000種。我是不是告訴過你這個查詢其實非常簡單嗎?
  • 2) 我大叫一聲辭了這份工作
    很有誘惑力,但是這樣一來,你不會的到查詢結果,而我需要錢來付賬單。
  • 3) 我只嘗試幾種執行計劃,挑一個成本最低的。
    由于不是超人,我不能算出所有計劃的成本。相反,我可以武斷地從全部可能的計劃中選擇一個子集,計算它們的成本,把最佳的計劃給你。
  • 4) 我用聰明的規則來降低可能性的數量
    有兩種規則:
    我可以用『邏輯』規則,它能去除無用的可能性,但是無法過濾大量的可能性。比如: 『嵌套聯接的內關系必須是最小的數據集』。
    我接受現實,不去找最佳方案,用更激進的規則來大大降低可能性的數量。比如:『如果一個關系很小,使用嵌套循環聯接,絕不使用合并或哈希聯接。』

在這個簡單的例子中,我最后得到很多可能性。但現實世界的查詢還會有其他關系運算符,像 OUTER JOIN, CROSS JOIN, GROUP BY, ORDER BY, PROJECTION, UNION, INTERSECT, DISTINCT … 這意味著更多的可能性。

那么,數據庫是如何處理的呢?

動態編程,貪婪算法和啟發式算法

關系型數據庫會嘗試我剛剛提到的多種方法,優化器真正的工作是在有限時間里找到一個好的解決方案。

多數時候,優化器找到的不是最佳的方案,而是一個『不錯』的

對于小規模的查詢,采取粗暴的方式是有可能的。但是為了讓中等規模的查詢也能采取粗暴的方式,我們有辦法避免不必要的計算,這就是動態編程。

動態編程

這幾個字背后的理念是,很多執行計劃是非常相似的。看看下圖這幾種計劃:

它們都有相同的子樹(A JOIN B),所以,不必在每個計劃中計算這個子樹的成本,計算一次,保存結果,當再遇到這個子樹時重用。用更正規的說法,我們面對的是個重疊問題。為了避免對部分結果的重復計算,我們使用記憶法。

對于計算機極客,下面是我在先前給你的教程里找到的一個算法。我不提供解釋,所以僅在你已經了解動態編程或者精通算法的情況下閱讀(我提醒過你哦):

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

procedurefindbestplan(S)

if(bestplan[S].costinfinite)

??returnbestplan[S]

// else bestplan[S] has not been computed earlier, compute it now

if(Scontainsonly1relation)

????????setbestplan[S].planandbestplan[S].costbasedonthebestway

????????ofaccessingS??/* Using selections on S and indices on S */

????elseforeachnon-emptysubsetS1ofSsuchthatS1!=S

??P1=findbestplan(S1)

??P2=findbestplan(S-S1)

??A=bestalgorithmforjoiningresultsofP1andP2

??cost=P1.cost+P2.cost+costofA

??ifcost<bestplan[S].cost

??????bestplan[S].cost=cost

??????bestplan[S].plan=executeP1.plan;executeP2.plan;

????????????????joinresultsofP1andP2usingA』

returnbestplan[S]

針對大規模查詢,你也可以用動態編程方法,但是要附加額外的規則(或者稱為啟發式算法)來減少可能性。

  • 如果我們僅分析一個特定類型的計劃(例如左深樹 left-deep tree,參考),我們得到 n*2^n 而不是 3^n。

  • 如果我們加上邏輯規則來避免一些模式的計劃(像『如果一個表有針對指定謂詞的索引,就不要對表嘗試合并聯接,要對索引』),就會在不給最佳方案造成過多傷害的前提下,減少可能性的數量。【譯者注:原文應該是有兩處筆誤: as=has, to=too】
  • 如果我們在流程里增加規則(像『聯接運算先于其他所有的關系運算』),也能減少大量的可能性。
  • ……

貪婪算法

但是,優化器面對一個非常大的查詢,或者為了盡快找到答案(然而查詢速度就快不起來了),會應用另一種算法,叫貪婪算法。

原理是按照一個規則(或啟發)以漸進的方式制定查詢計劃。在這個規則下,貪婪算法逐步尋找最佳算法,先處理一條JOIN,接著每一步按照同樣規則加一條新的JOIN。

我們來看個簡單的例子。比如一個針對5張表(A,B,C,D,E)4次JOIN 的查詢,為了簡化我們把嵌套JOIN作為可能的聯接方式,按照『使用最低成本的聯接』規則。

  • 直接從 5 個表里選一個開始(比如 A)
  • 計算每一個與 A 的聯接(A 作為內關系或外關系)
  • 發現 “A JOIN B” 成本最低
  • 計算每一個與 “A JOIN B” 的結果聯接的成本(“A JOIN B” 作為內關系或外關系)
  • 發現 “(A JOIN B) JOIN C” 成本最低
  • 計算每一個與 “(A JOIN B) JOIN C” 的結果聯接的成本 ……
  • 最后確定執行計劃 “( ( (A JOIN B) JOIN C) JOIN D ) JOIN E )”

因為我們是武斷地從表 A 開始,我們可以把同樣的算法用在 B,然后 C,然后 D, 然后 E。最后保留成本最低的執行計劃。

順便說一句,這個算法有個名字,叫『最近鄰居算法』。

拋開細節不談,只需一個良好的模型和一個 N*log(N) 復雜度的排序,問題就輕松解決了。這個算法的復雜度是 O(N*log(N)) ,對比一下完全動態編程的 O(3^N)。如果你有個20個聯接的大型查詢,這意味著 26 vs 3,486,784,401 ,天壤之別!

這個算法的問題是,我們做的假設是:找到 2 個表的最佳聯接方法,保留這個聯接結果,再聯接下一個表,就能得到最低的成本。但是:

  • 即使在 A, B, C 之間,A JOIN B 可得最低成本
  • (A JOIN C) JOIN B 也許比 (A JOIN B) JOIN C 更好。

為了改善這一狀況,你可以多次使用基于不同規則的貪婪算法,并保留最佳的執行計劃。

其他算法

[ 如果你已經受夠了算法話題,就直接跳到下一部分。這部分對文章余下的內容不重要。]【譯者注:我也很想把這段跳過去 -_- 】

很多計算機科學研究者熱衷于尋找最佳的執行計劃,他們經常為特定問題或模式探尋更好的解決方案,比如:

  • 如果查詢是星型聯接(一種多聯接查詢),某些數據庫使用一種特定的算法。
  • 如果查詢是并行的,某些數據庫使用一種特定的算法。 ……

其他算法也在研究之中,就是為了替換在大型查詢中的動態編程算法。貪婪算法屬于一個叫做啟發式算法的大家族,它根據一條規則(或啟發),保存上一步找到的方法,『附加』到當前步驟來進一步搜尋解決方法。有些算法根據特定規則,一步步的應用規則但不總是保留上一步找到的最佳方法。它們統稱啟發式算法。

比如,基因算法就是一種:

  • 一個方法代表一種可能的完整查詢計劃
  • 每一步保留了 P 個方法(即計劃),而不是一個。
  • 0) P 個計劃隨機創建
  • 1) 成本最低的計劃才會保留
  • 2) 這些最佳計劃混合在一起產生 P 個新的計劃
  • 3) 一些新的計劃被隨機改寫
  • 4) 1,2,3步重復 T 次
  • 5) 然后在最后一次循環,從 P 個計劃里得到最佳計劃。

循環次數越多,計劃就越好。

這是魔術?不,這是自然法則:適者生存!

PostgreSQL?實現了基因算法,但我并沒有發現它是不是默認使用這種算法的。

數據庫中還使用了其它啟發式算法,像『模擬退火算法(Simulated Annealing)』、『交互式改良算法(Iterative Improvement)』、『雙階段優化算法(Two-Phase Optimization)』…..不過,我不知道這些算法當前是否在企業級數據庫應用了,還是僅僅用在研究型數據庫。

如果想進一步了解,這篇研究文章介紹兩個更多可能的算法《數據庫查詢優化中聯接排序問題的算法綜述》,你可以去閱讀一下。

真實的優化器

[ 這段不重要,可以跳過 ]

然而,所有上述羅里羅嗦的都非常理論化,我是個開發者而不是研究者,我喜歡具體的例子。

我們來看看?SQLite 優化器?是怎么工作的。這是個輕量化數據庫,它使用一種簡單優化器,基于帶有附加規則的貪婪算法,來限制可能性的數量。

  • SQLite 在有 CROSS JOIN 操作符時從不給表重新排序
  • 使用嵌套聯接
  • 外聯接始終按順序評估
  • ……
  • 3.8.0之前的版本使用『最近鄰居』貪婪算法來搜尋最佳查詢計劃
    等等……我們見過這個算法!真是巧哈!
  • 從3.8.0版本(發布于2015年)開始,SQLite使用『N最近鄰居』貪婪算法來搜尋最佳查詢計劃

我們再看看另一個優化器是怎么工作的。IBM DB2 跟所有企業級數據庫都類似,我討論它是因為在切換到大數據之前,它是我最后真正使用的數據庫。

看過官方文檔后,我們了解到 DB2 優化器可以讓你使用 7 種級別的優化:

  • 對聯接使用貪婪算法
  • ? ? 0 – 最小優化,使用索引掃描和嵌套循環聯接,避免一些查詢重寫
    • ? ? 1 – 低級優化
    • ? ? 2 – 完全優化
  • 對聯接使用動態編程算法
  • ? ? 3 – 中等優化和粗略的近似法
    • ? ??5 – 完全優化,使用帶有啟發式的所有技術
    • ? ??7 – 完全優化,類似級別5,但不用啟發式
    • ? ??9 – 最大優化,完全不顧開銷,考慮所有可能的聯接順序,包括笛卡爾乘積

可以看到?DB2 使用貪婪算法和動態編程算法。當然,他們不會把自己的啟發算法分享出來的,因為查詢優化器是數據庫的看家本領。

DB2 的默認級別是 5,優化器使用下列特性:?【譯者注:以下出現的一些概念我沒有做考證,因為[ 這段不重要,可以跳過 ]】

  • 使用所有可用的統計,包括線段樹(frequent-value)和分位數統計(quantile statistics)。
  • 使用所有查詢重寫規則(含物化查詢表路由,materialized query table routing),除了在極少情況下適用的計算密集型規則。
  • 使用動態編程模擬聯接
  • ? ? 有限使用組合內關系(composite inner relation)
  • ? ? 對于涉及查找表的星型模式,有限使用笛卡爾乘積
  • 考慮寬泛的訪問方式,含列表預取(list prefetch,注:我們將討論什么是列表預取),index ANDing(注:一種對索引的特殊操作),和物化查詢表路由。

默認的,DB2 對聯接排列使用受啟發式限制的動態編程算法。

其它情況 (GROUP BY, DISTINCT…) 由簡單規則處理。

查詢計劃緩存

由于創建查詢計劃是耗時的,大多數據庫把計劃保存在查詢計劃緩存,來避免重復計算。這個話題比較大,因為數據庫需要知道什么時候更新過時的計劃。辦法是設置一個上限,如果一個表的統計變化超過了上限,關于該表的查詢計劃就從緩存中清除。

查詢執行器

在這個階段,我們有了一個優化的執行計劃,再編譯為可執行代碼。然后,如果有足夠資源(內存,CPU),查詢執行器就會執行它。計劃中的操作符 (JOIN, SORT BY …) 可以順序或并行執行,這取決于執行器。為了獲得和寫入數據,查詢執行器與數據管理器交互,本文下一部分來討論數據管理器。

數據管理器

在這一步,查詢管理器執行了查詢,需要從表和索引獲取數據,于是向數據管理器提出請求。但是有 2 個問題:

  • 關系型數據庫使用事務模型,所以,當其他人在同一時刻使用或修改數據時,你無法得到這部分數據。
  • 數據提取是數據庫中速度最慢的操作,所以數據管理器需要足夠聰明地獲得數據并保存在內存緩沖區內。

在這一部分,我沒看看關系型數據庫是如何處理這兩個問題的。我不會講數據管理器是怎么獲得數據的,因為這不是最重要的(而且本文已經夠長的了!)。

緩存管理器

我已經說過,數據庫的主要瓶頸是磁盤 I/O。為了提高性能,現代數據庫使用緩存管理器。

查詢執行器不會直接從文件系統拿數據,而是向緩存管理器要。緩存管理器有一個內存緩存區,叫做緩沖池,從內存讀取數據顯著地提升數據庫性能。對此很難給出一個數量級,因為這取決于你需要的是哪種操作:

  • 順序訪問(比如:全掃描) vs 隨機訪問(比如:按照row id訪問)
  • 讀還是寫

以及數據庫使用的磁盤類型:

  • 7.2k/10k/15k rpm的硬盤
  • SSD
  • RAID 1/5/…

要我說,內存比磁盤要快100到10萬倍。

然而,這導致了另一個問題(數據庫總是這樣…),緩存管理器需要在查詢執行器使用數據之前得到數據,否則查詢管理器不得不等待數據從緩慢的磁盤中讀出來。

預讀

這個問題叫預讀。查詢執行器知道它將需要什么數據,因為它了解整個查詢流,而且通過統計也了解磁盤上的數據。道理是這樣的:

  • 當查詢執行器處理它的第一批數據時
  • 會告訴緩存管理器預先裝載第二批數據
  • 當開始處理第二批數據時
  • 告訴緩存管理器預先裝載第三批數據,并且告訴緩存管理器第一批可以從緩存里清掉了。
  • ……

緩存管理器在緩沖池里保存所有的這些數據。為了確定一條數據是否有用,緩存管理器給緩存的數據添加了額外的信息(叫閂鎖)。

有時查詢執行器不知道它需要什么數據,有的數據庫也不提供這個功能。相反,它們使用一種推測預讀法(比如:如果查詢執行器想要數據1、3、5,它不久后很可能會要 7、9、11),或者順序預讀法(這時候緩存管理器只是讀取一批數據后簡單地從磁盤加載下一批連續數據)。

為了監控預讀的工作狀況,現代數據庫引入了一個度量叫緩沖/緩存命中率,用來顯示請求的數據在緩存中找到而不是從磁盤讀取的頻率。

注:糟糕的緩存命中率不總是意味著緩存工作狀態不佳。更多信息請閱讀Oracle文檔。

緩沖只是容量有限的內存空間,因此,為了加載新的數據,它需要移除一些數據。加載和清除緩存需要一些磁盤和網絡I/O的成本。如果你有個經常執行的查詢,那么每次都把查詢結果加載然后清除,效率就太低了。現代數據庫用緩沖區置換策略來解決這個問題。

緩沖區置換策略

多數現代數據庫(至少 SQL Server, MySQL,?oracle?和 DB2)使用 LRU 算法。

LRU

LRU代表最近最少使用(Least?Recently?Used)算法,背后的原理是:在緩存里保留的數據是最近使用的,所以更有可能再次使用。

圖解:

為了更好的理解,我假設緩沖區里的數據沒有被閂鎖鎖住(就是說是可以被移除的)。在這個簡單的例子里,緩沖區可以保存 3 個元素:

  • 1:緩存管理器(簡稱CM)使用數據1,把它放入空的緩沖區
  • 2:CM使用數據4,把它放入半載的緩沖區
  • 3:CM使用數據3,把它放入半載的緩沖區
  • 4:CM使用數據9,緩沖區滿了,所以數據1被清除,因為它是最后一個最近使用的,數據9加入到緩沖區
  • 5:CM使用數據4,數據4已經在緩沖區了,所以它再次成為第一個最近使用的。
  • 6:CM使用數據1,緩沖區滿了,所以數據9被清除,因為它是最后一個最近使用的,數據1加入到緩沖區
  • ……

這個算法效果很好,但是有些限制。如果對一個大表執行全表掃描怎么辦?換句話說,當表/索引的大小超出緩沖區會發生什么?使用這個算法會清除之前緩存內所有的數據,而且全掃描的數據很可能只使用一次。

改進

為了防止這個現象,有些數據庫增加了特殊的規則,比如Oracle文檔中的描述:

『對非常大的表來說,數據庫通常使用直接路徑來讀取,即直接加載區塊[……],來避免填滿緩沖區。對于中等大小的表,數據庫可以使用直接讀取或緩存讀取。如果選擇緩存讀取,數據庫把區塊置于LRU的尾部,防止清空當前緩沖區。』

還有一些可能,比如使用高級版本的LRU,叫做 LRU-K。例如,SQL Server 使用 LRU-2。

這個算法的原理是把更多的歷史記錄考慮進來。簡單LRU(也就是 LRU-1),只考慮最后一次使用的數據。LRU-K呢:

  • 考慮數據最后第K次使用的情況
  • 數據使用的次數加進了權重
  • 一批新數據加載進入緩存,舊的但是經常使用的數據不會被清除(因為權重更高)
  • 但是這個算法不會保留緩存中不再使用的數據
  • 所以數據如果不再使用,權重值隨著時間推移而降低

計算權重是需要成本的,所以SQL Server只是使用 K=2,這個值性能不錯而且額外開銷可以接受。

關于LRU-K更深入的知識,可以閱讀早期的研究論文(1993):數據庫磁盤緩沖的LRU-K頁面置換算法

其他算法

當然還有其他管理緩存的算法,比如:

  • 2Q(類LRU-K算法)
  • CLOCK(類LRU-K算法)
  • MRU(最新使用的算法,用LRU同樣的邏輯但不同的規則)
  • LRFU(Least Recently and Frequently Used,最近最少使用最近最不常用
  • ……

寫緩沖區

我只探討了讀緩存 —— 在使用之前預先加載數據。用來保存數據、成批刷入磁盤,而不是逐條寫入數據從而造成很多單次磁盤訪問。

要記住,緩沖區保存的是頁(最小的數據單位)而不是行(邏輯上/人類習慣的觀察數據的方式)。緩沖池內的頁如果被修改了但還沒有寫入磁盤,就是臟頁。有很多算法來決定寫入臟頁的最佳時機,但這個問題與事務的概念高度關聯,下面我們就談談事務。

事務管理器

最后但同樣重要的,是事務管理器,我們將看到這個進程是如何保證每個查詢在自己的事務內執行的。但開始之前,我們需要理解ACID事務的概念。

“I’m on acid”

一個ACID事務是一個工作單元,它要保證4個屬性:

  • 原子性(Atomicity): 事務『要么全部完成,要么全部取消』,即使它持續運行10個小時。如果事務崩潰,狀態回到事務之前(事務回滾)。
  • 隔離性(Isolation): 如果2個事務 A 和 B 同時運行,事務 A 和 B 最終的結果是相同的,不管 A 是結束于 B 之前/之后/運行期間。
  • 持久性(Durability): 一旦事務提交(也就是成功執行),不管發生什么(崩潰或者出錯),數據要保存在數據庫中。
  • 一致性(Consistency): 只有合法的數據(依照關系約束和函數約束)能寫入數據庫,一致性與原子性和隔離性有關。

在同一個事務內,你可以運行多個SQL查詢來讀取、創建、更新和刪除數據。當兩個事務使用相同的數據,麻煩就來了。經典的例子是從賬戶A到賬戶B的匯款。假設有2個事務:

  • 事務1(T1)從賬戶A取出100美元給賬戶B
  • 事務2(T2)從賬戶A取出50美元給賬戶B

我們回來看看ACID屬性:

  • 原子性確保不管 T1 期間發生什么(服務器崩潰、網絡中斷…),你不能出現賬戶A 取走了100美元但沒有給賬戶B 的現象(這就是數據不一致狀態)。
  • 隔離性確保如果 T1 和 T2 同時發生,最終A將減少150美元,B將得到150美元,而不是其他結果,比如因為 T2 部分抹除了 T1 的行為,A減少150美元而B只得到50美元(這也是不一致狀態)。
  • 持久性確保如果 T1 剛剛提交,數據庫就發生崩潰,T1 不會消失得無影無蹤。
  • 一致性確保錢不會在系統內生成或滅失。

[以下部分不重要,可以跳過]

現代數據庫不會使用純粹的隔離作為默認模式,因為它會帶來巨大的性能消耗。SQL一般定義4個隔離級別:

  • 串行化(Serializable,SQLite默認模式):最高級別的隔離。兩個同時發生的事務100%隔離,每個事務有自己的『世界』。
  • 可重復讀(Repeatable read,MySQL默認模式):每個事務有自己的『世界』,除了一種情況。如果一個事務成功執行并且添加了新數據,這些數據對其他正在執行的事務是可見的。但是如果事務成功修改了一條數據,修改結果對正在運行的事務不可見。所以,事務之間只是在新數據方面突破了隔離,對已存在的數據仍舊隔離。
    舉個例子,如果事務A運行”SELECT count(1) from TABLE_X” ,然后事務B在 TABLE_X 加入一條新數據并提交,當事務A再運行一次 count(1)結果不會是一樣的。
    這叫幻讀(phantom read)。
  • 讀取已提交(Read committed,Oracle、PostgreSQL、SQL Server默認模式):可重復讀+新的隔離突破。如果事務A讀取了數據D,然后數據D被事務B修改(或刪除)并提交,事務A再次讀取數據D時數據的變化(或刪除)是可見的。
    這叫不可重復讀(non-repeatable read)。
  • 讀取未提交(Read uncommitted):最低級別的隔離,是讀取已提交+新的隔離突破。如果事務A讀取了數據D,然后數據D被事務B修改(但并未提交,事務B仍在運行中),事務A再次讀取數據D時,數據修改是可見的。如果事務B回滾,那么事務A第二次讀取的數據D是無意義的,因為那是事務B所做的從未發生的修改(已經回滾了嘛)。
    這叫臟讀(dirty read)。

多數數據庫添加了自定義的隔離級別(比如 PostgreSQL、Oracle、SQL Server的快照隔離),而且并沒有實現SQL規范里的所有級別(尤其是讀取未提交級別)。

默認的隔離級別可以由用戶/開發者在建立連接時覆蓋(只需要增加很簡單的一行代碼)。

并發控制

確保隔離性、一致性和原子性的真正問題是對相同數據的寫操作(增、更、刪):

  • 如果所有事務只是讀取數據,它們可以同時工作,不會更改另一個事務的行為。
  • 如果(至少)有一個事務在修改其他事務讀取的數據,數據庫需要找個辦法對其它事務隱藏這種修改。而且,它還需要確保這個修改操作不會被另一個看不到這些數據修改的事務擦除。

這個問題叫并發控制。

最簡單的解決辦法是依次執行每個事務(即順序執行),但這樣就完全沒有伸縮性了,在一個多處理器/多核服務器上只有一個核心在工作,效率很低。

理想的辦法是,每次一個事務創建或取消時:

  • 監控所有事務的所有操作
  • 檢查是否2個(或更多)事務的部分操作因為讀取/修改相同的數據而存在沖突
  • 重新編排沖突事務中的操作來減少沖突的部分
  • 按照一定的順序執行沖突的部分(同時非沖突事務仍然在并發運行)
  • 考慮事務有可能被取消

用更正規的說法,這是對沖突的調度問題。更具體點兒說,這是個非常困難而且CPU開銷很大的優化問題。企業級數據庫無法承擔等待幾個小時,來尋找每個新事務活動最好的調度,因此就使用不那么理想的方式以避免更多的時間浪費在解決沖突上。

鎖管理器

為了解決這個問題,多數數據庫使用鎖和/或數據版本控制。這是個很大的話題,我會集中探討鎖,和一點點數據版本控制。

悲觀鎖

原理是:

  • 如果一個事務需要一條數據
  • 它就把數據鎖住
  • 如果另一個事務也需要這條數據
  • 它就必須要等第一個事務釋放這條數據
    這個鎖叫排他鎖。

但是對一個僅僅讀取數據的事務使用排他鎖非常昂貴,因為這會迫使其它只需要讀取相同數據的事務等待。因此就有了另一種鎖,共享鎖。

共享鎖是這樣的:

  • 如果一個事務只需要讀取數據A
  • 它會給數據A加上『共享鎖』并讀取
  • 如果第二個事務也需要僅僅讀取數據A
  • 它會給數據A加上『共享鎖』并讀取
  • 如果第三個事務需要修改數據A
  • 它會給數據A加上『排他鎖』,但是必須等待另外兩個事務釋放它們的共享鎖。

同樣的,如果一塊數據被加上排他鎖,一個只需要讀取該數據的事務必須等待排他鎖釋放才能給該數據加上共享鎖。

鎖管理器是添加和釋放鎖的進程,在內部用一個哈希表保存鎖信息(關鍵字是被鎖的數據),并且了解每一塊數據是:

  • 被哪個事務加的鎖
  • 哪個事務在等待數據解鎖

死鎖

但是使用鎖會導致一種情況,2個事務永遠在等待一塊數據:

在本圖中:

  • 事務A 給 數據1 加上排他鎖并且等待獲取數據2
  • 事務B 給 數據2 加上排他鎖并且等待獲取數據1

這叫死鎖。

在死鎖發生時,鎖管理器要選擇取消(回滾)一個事務,以便消除死鎖。這可是個艱難的決定:

  • 殺死數據修改量最少的事務(這樣能減少回滾的成本)?
  • 殺死持續時間最短的事務,因為其它事務的用戶等的時間更長?
  • 殺死能用更少時間結束的事務(避免可能的資源饑荒)?
  • 一旦發生回滾,有多少事務會受到回滾的影響?

在作出選擇之前,鎖管理器需要檢查是否有死鎖存在。

哈希表可以看作是個圖表(見上文圖),圖中出現循環就說明有死鎖。由于檢查循環是昂貴的(所有鎖組成的圖表是很龐大的),經常會通過簡單的途徑解決:使用超時設定。如果一個鎖在超時時間內沒有加上,那事務就進入死鎖狀態。

鎖管理器也可以在加鎖之前檢查該鎖會不會變成死鎖,但是想要完美的做到這一點還是很昂貴的。因此這些預檢經常設置一些基本規則。

兩段鎖

實現純粹的隔離最簡單的方法是:事務開始時獲取鎖,結束時釋放鎖。就是說,事務開始前必須等待確保自己能加上所有的鎖,當事務結束時釋放自己持有的鎖。這是行得通的,但是為了等待所有的鎖,大量的時間被浪費了。

更快的方法是兩段鎖協議(Two-Phase Locking Protocol,由 DB2 和 SQL Server使用),在這里,事務分為兩個階段:

  • 成長階段:事務可以獲得鎖,但不能釋放鎖。
  • 收縮階段:事務可以釋放鎖(對于已經處理完而且不會再次處理的數據),但不能獲得新鎖。

這兩條簡單規則背后的原理是:

  • 釋放不再使用的鎖,來降低其它事務的等待時間
  • 防止發生這類情況:事務最初獲得的數據,在事務開始后被修改,當事務重新讀取該數據時發生不一致。

這個規則可以很好地工作,但有個例外:如果修改了一條數據、釋放了關聯的鎖后,事務被取消(回滾),而另一個事務讀到了修改后的值,但最后這個值卻被回滾。為了避免這個問題,所有獨占鎖必須在事務結束時釋放。

多說幾句

當然了,真實的數據庫使用更復雜的系統,涉及到更多類型的鎖(比如意向鎖,intention locks)和更多的粒度(行級鎖、頁級鎖、分區鎖、表鎖、表空間鎖),但是道理是相同的。

我只探討純粹基于鎖的方法,數據版本控制是解決這個問題的另一個方法。

版本控制是這樣的:

  • 每個事務可以在相同時刻修改相同的數據
  • 每個事務有自己的數據拷貝(或者叫版本)
  • 如果2個事務修改相同的數據,只接受一個修改,另一個將被拒絕,相關的事務回滾(或重新運行)

這將提高性能,因為:

  • 讀事務不會阻塞寫事務
  • 寫事務不會阻塞讀
  • 沒有『臃腫緩慢』的鎖管理器帶來的額外開銷

除了兩個事務寫相同數據的時候,數據版本控制各個方面都比鎖表現得更好。只不過,你很快就會發現磁盤空間消耗巨大。

數據版本控制和鎖機制是兩種不同的見解:樂觀鎖和悲觀鎖。兩者各有利弊,完全取決于使用場景(讀多還是寫多)。關于數據版本控制,我推薦這篇非常優秀的文章,講的是PostgreSQL如何實現多版本并發控制的。

一些數據庫,比如DB2(直到版本 9.7)和 SQL Server(不含快照隔離)僅使用鎖機制。其他的像PostgreSQL, MySQL 和 Oracle 使用鎖和鼠標版本控制混合機制。我不知道是否有僅用版本控制的數據庫(如果你知道請告訴我)。

[2015-08-20更新]一名讀者告訴我:

Firebird 和 Interbase 用不帶鎖的版本控制。

版本控制對索引的影響挺有趣的:有時唯一索引會出現重復,索引的條目會多于表行數,等等。

如果你讀過不同級別的隔離那部分內容,你會知道,提高隔離級別就會增加鎖的數量和事務等待加鎖的時間。這就是為什么多數數據庫默認不會使用最高級別的隔離(即串行化)。

當然,你總是可以自己去主流數據庫(像MySQL,?PostgreSQL?或?Oracle)的文檔里查一下。

日志管理器

我們已經知道,為了提升性能,數據庫把數據保存在內存緩沖區內。但如果當事務提交時服務器崩潰,崩潰時還在內存里的數據會丟失,這破壞了事務的持久性。

你可以把所有數據都寫在磁盤上,但是如果服務器崩潰,最終數據可能只有部分寫入磁盤,這破壞了事務的原子性。

事務作出的任何修改必須是或者撤銷,或者完成。

有 2 個辦法解決這個問題:

  • 影子副本/頁(Shadow copies/pages):每個事務創建自己的數據庫副本(或部分數據庫的副本),并基于這個副本來工作。一旦出錯,這個副本就被移除;一旦成功,數據庫立即使用文件系統的一個把戲,把副本替換到數據中,然后刪掉『舊』數據。
  • 事務日志(Transaction log):事務日志是一個存儲空間,在每次寫盤之前,數據庫在事務日志中寫入一些信息,這樣當事務崩潰或回滾,數據庫知道如何移除或完成尚未完成的事務。

WAL(預寫式日志)

影子副本/頁在運行較多事務的大型數據庫時制造了大量磁盤開銷,所以現代數據庫使用事務日志。事務日志必須保存在穩定的存儲上,我不會深挖存儲技術,但至少RAID磁盤是必須的,以防磁盤故障。

多數數據庫(至少是Oracle,?SQL Server,?DB2,?PostgreSQL, MySQL 和?SQLite)?使用預寫日志協議(Write-Ahead Logging protocol ,WAL)來處理事務日志。WAL協議有 3 個規則:

  • 1) 每個對數據庫的修改都產生一條日志記錄,在數據寫入磁盤之前日志記錄必須寫入事務日志。
  • 2) 日志記錄必須按順序寫入;記錄 A 發生在記錄 B 之前,則 A 必須寫在 B 之前。
  • 3) 當一個事務提交時,在事務成功之前,提交順序必須寫入到事務日志。

這個工作由日志管理器完成。簡單的理解就是,日志管理器處于緩存管理器(cache manager)和數據訪問管理器(data access manager,負責把數據寫入磁盤)之間,每個 update / delete / create / commit / rollback 操作在寫入磁盤之前先寫入事務日志。簡單,對吧?

回答錯誤! 我們研究了這么多內容,現在你應該知道與數據庫相關的每一件事都帶著『數據庫效應』的詛咒。好吧,我們說正經的,問題在于,如何找到寫日志的同時保持良好的性能的方法。如果事務日志寫得太慢,整體都會慢下來。

ARIES

1992年,IBM 研究人員『發明』了WAL的增強版,叫 ARIES。ARIES 或多或少地在現代數據庫中使用,邏輯未必相同,但AIRES背后的概念無處不在。我給發明加了引號是因為,按照MIT這門課的說法,IBM 的研究人員『僅僅是寫了事務恢復的最佳實踐方法』。AIRES 論文發表的時候我才 5 歲,我不關心那些酸溜溜的科研人員老掉牙的閑言碎語。事實上,我提及這個典故,是在開始探討最后一個技術點前讓你輕松一下。我閱讀過這篇 ARIES 論文?的大量篇幅,發現它很有趣。在這一部分我只是簡要的談一下 ARIES,不過我強烈建議,如果你想了解真正的知識,就去讀那篇論文。

ARIES 代表『數據庫恢復原型算法』(Algorithms for?Recovery and?Isolation?Exploiting?Semantics)。

這個技術要達到一個雙重目標:

  • 1)?寫日志的同時保持良好性能
  • 2) 快速和可靠的數據恢復

有多個原因讓數據庫不得不回滾事務:

  • 因為用戶取消
  • 因為服務器或網絡故障
  • 因為事務破壞了數據庫完整性(比如一個列有唯一性約束而事務添加了重復值)
  • 因為死鎖

有時候(比如網絡出現故障),數據庫可以恢復事務。

這怎么可能呢?為了回答這個問題,我們需要了解日志里保存的信息。

日志

事務的每一個操作(增/刪/改)產生一條日志,由如下內容組成:

  • LSN:一個唯一的日志序列號(Log Sequence Number)。LSN是按時間順序分配的 * ,這意味著如果操作 A 先于操作 B,log A 的 LSN 要比 log B 的 LSN 小。
  • TransID:產生操作的事務ID。
  • PageID:被修改的數據在磁盤上的位置。磁盤數據的最小單位是頁,所以數據的位置就是它所處頁的位置。
  • PrevLSN:同一個事務產生的上一條日志記錄的鏈接。
  • UNDO:取消本次操作的方法。
    比如,如果操作是一次更新,UNDO將或者保存元素更新前的值/狀態(物理UNDO),或者回到原來狀態的反向操作(邏輯UNDO) **。
  • REDO:重復本次操作的方法。 同樣的,有 2 種方法:或者保存操作后的元素值/狀態,或者保存操作本身以便重復。
  • …:(供您參考,一個 ARIES 日志還有 2 個字段:UndoNxtLSN 和 Type)。

進一步說,磁盤上每個頁(保存數據的,不是保存日志的)都記錄著最后一個修改該數據操作的LSN。

*LSN的分配其實更復雜,因為它關系到日志存儲的方式。但道理是相同的。

** ARIES 只使用邏輯UNDO,因為處理物理UNDO太過混亂了。

注:據我所知,只有 PostgreSQL 沒有使用UNDO,而是用一個垃圾回收服務來刪除舊版本的數據。這個跟 PostgreSQL 對數據版本控制的實現有關。

為了更好的說明這一點,這有一個簡單的日志記錄演示圖,是由查詢 “UPDATE FROM PERSON SET AGE = 18;” 產生的,我們假設這個查詢是事務18執行的。【譯者注: SQL 語句原文如此,應該是作者筆誤 】

每條日志都有一個唯一的LSN,鏈接在一起的日志屬于同一個事務。日志按照時間順序鏈接(鏈接列表的最后一條日志是最后一個操作產生的)。

日志緩沖區

為了防止寫日志成為主要的瓶頸,數據庫使用了日志緩沖區。

當查詢執行器要求做一次修改:

  • 1) 緩存管理器將修改存入自己的緩沖區;
  • 2) 日志管理器將相關的日志存入自己的緩沖區;
  • 3) 到了這一步,查詢執行器認為操作完成了(因此可以請求做另一次修改);
  • 4) 接著(不久以后)日志管理器把日志寫入事務日志,什么時候寫日志由某算法來決定。
  • 5) 接著(不久以后)緩存管理器把修改寫入磁盤,什么時候寫盤由某算法來決定。

當事務提交,意味著事務每一個操作的 1 2 3 4 5 步驟都完成了。寫事務日志是很快的,因為它只是『在事務日志某處增加一條日志』;而數據寫盤就更復雜了,因為要用『能夠快速讀取的方式寫入數據』。

STEAL 和 FORCE 策略

出于性能方面的原因,第 5 步有可能在提交之后完成,因為一旦發生崩潰,還有可能用REDO日志恢復事務。這叫做?NO-FORCE策略。

數據庫可以選擇FORCE策略(比如第 5 步在提交之前必須完成)來降低恢復時的負載。

另一個問題是,要選擇數據是一步步的寫入(STEAL策略),還是緩沖管理器需要等待提交命令來一次性全部寫入(NO-STEAL策略)。選擇STEAL還是NO-STEAL取決于你想要什么:快速寫入但是從 UNDO 日志恢復緩慢,還是快速恢復。

總結一下這些策略對恢復的影響:

  • STEAL/NO-FORCE 需要 UNDO 和 REDO: 性能高,但是日志和恢復過程更復雜 (比如 ARIES)。多數數據庫選擇這個策略。 注:這是我從多個學術論文和教程里看到的,但并沒有看到官方文檔里顯式說明這一點。
  • STEAL/ FORCE 只需要 UNDO.
  • NO-STEAL/NO-FORCE 只需要 REDO.
  • NO-STEAL/FORCE 什么也不需要:?性能最差,而且需要巨大的內存。

關于恢復

Ok,有了不錯的日志,我們來用用它們!

假設新來的實習生讓數據庫崩潰了(首要規矩:永遠是實習生的錯。),你重啟了數據庫,恢復過程開始了。

ARIES從崩潰中恢復有三個階段:

  • 1)?分析階段:恢復進程讀取全部事務日志,來重建崩潰過程中所發生事情的時間線,決定哪個事務要回滾(所有未提交的事務都要回滾)、崩潰時哪些數據需要寫盤。
  • 2)?Redo階段:這一關從分析中選中的一條日志記錄開始,使用 REDO 來將數據庫恢復到崩潰之前的狀態。

在REDO階段,REDO日志按照時間順序處理(使用LSN)。

對每一條日志,恢復進程需要讀取包含數據的磁盤頁LSN。

如果LSN(磁盤頁)>= LSN(日志記錄),說明數據已經在崩潰前寫到磁盤(但是值已經被日志之后、崩潰之前的某個操作覆蓋),所以不需要做什么。

如果LSN(磁盤頁)< LSN(日志記錄),那么磁盤上的頁將被更新。

即使將被回滾的事務,REDO也是要做的,因為這樣簡化了恢復過程(但是我相信現代數據庫不會這么做的)。

  • 3)?Undo階段:這一階段回滾所有崩潰時未完成的事務。回滾從每個事務的最后一條日志開始,并且按照時間倒序處理UNDO日志(使用日志記錄的PrevLSN)。

?

恢復過程中,事務日志必須留意恢復過程的操作,以便寫入磁盤的數據與事務日志相一致。一個解決辦法是移除被取消的事務產生的日志記錄,但是這個太困難了。相反,ARIES在事務日志中記錄補償日志,來邏輯上刪除被取消的事務的日志記錄。

當事務被『手工』取消,或者被鎖管理器取消(為了消除死鎖),或僅僅因為網絡故障而取消,那么分析階段就不需要了。對于哪些需要 REDO 哪些需要 UNDO 的信息在 2 個內存表中:

  • 事務表(保存當前所有事務的狀態)
  • 臟頁表(保存哪些數據需要寫入磁盤)

當新的事務產生時,這兩個表由緩存管理器和事務管理器更新。因為是在內存中,當數據庫崩潰時它們也被破壞掉了。

分析階段的任務就是在崩潰之后,用事務日志中的信息重建上述的兩個表。為了加快分析階段,ARIES提出了一個概念:檢查點(check point),就是不時地把事務表和臟頁表的內容,還有此時最后一條LSN寫入磁盤。那么在分析階段當中,只需要分析這個LSN之后的日志即可。

結語

寫這篇文章之前,我知道這個題目有多大,也知道寫這樣一篇深入的文章會相當耗時。最后證明我過于樂觀了,實際上花了兩倍于預期的時間,但是我學到了很多。

如果你想很好地了解數據庫,我推薦這篇研究論文:《數據庫系統架構》,對數據庫有很好的介紹(共110頁),而且非計算機專業人士也能讀懂。這篇論文出色的幫助我制定了本文的寫作計劃,它沒有像本文那樣專注于數據結構和算法,更多的講了架構方面的概念。

如果你仔細閱讀了本文,你現在應該了解一個數據庫是多么的強大了。鑒于文章很長,讓我來提醒你我們都學到了什么:

  • B+樹索引概述
  • 數據庫的全局概述
  • 基于成本的優化概述,特別專注了聯接運算
  • 緩沖池管理概述
  • 事務管理概述

但是,數據庫包含了更多的聰明巧技。比如,我并沒有談到下面這些棘手的問題:

  • 如何管理數據庫集群和全局事務
  • 如何在數據庫運行的時候產生快照
  • 如何高效地存儲(和壓縮)數據
  • 如何管理內存

所以,當你不得不在問題多多的 NoSQL數據庫和堅如磐石的關系型數據庫之間抉擇的時候,要三思而行。不要誤會,某些 NoSQL數據庫是很棒的,但是它們畢竟還年輕,只是解決了少量應用關注的一些特定問題。

最后說一句,如果有人問你數據庫的原理是什么,你不用逃之夭夭,現在你可以回答:

或者,就讓他/她來看本文吧。

總結

以上是生活随笔為你收集整理的关系数据库是如何工作的的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

a国产一区二区免费入口 | √天堂资源地址中文在线 | 日本xxxx色视频在线观看免费 | 激情内射亚州一区二区三区爱妻 | 国产特级毛片aaaaaaa高清 | 亚洲成av人片在线观看无码不卡 | 午夜无码区在线观看 | 欧洲精品码一区二区三区免费看 | 撕开奶罩揉吮奶头视频 | yw尤物av无码国产在线观看 | 久久99精品国产麻豆 | 2020久久超碰国产精品最新 | 国产精品人人妻人人爽 | 亚洲日韩中文字幕在线播放 | 久久亚洲中文字幕无码 | 午夜福利试看120秒体验区 | 日韩av无码一区二区三区不卡 | 大肉大捧一进一出好爽视频 | 亚洲成色在线综合网站 | 午夜精品一区二区三区在线观看 | 天海翼激烈高潮到腰振不止 | 国产午夜手机精彩视频 | 国产精华av午夜在线观看 | 成人性做爰aaa片免费看不忠 | 精品国偷自产在线视频 | 精品熟女少妇av免费观看 | 无套内射视频囯产 | 亚洲精品欧美二区三区中文字幕 | 亚洲娇小与黑人巨大交 | 久久天天躁狠狠躁夜夜免费观看 | 少妇太爽了在线观看 | 在教室伦流澡到高潮hnp视频 | 波多野结衣av一区二区全免费观看 | 欧美人妻一区二区三区 | 成人无码视频免费播放 | 国产精品无码成人午夜电影 | 日本免费一区二区三区最新 | 日本成熟视频免费视频 | 麻豆精品国产精华精华液好用吗 | 亚洲の无码国产の无码影院 | 亚洲成av人在线观看网址 | 老司机亚洲精品影院无码 | 亚洲国产成人av在线观看 | 大肉大捧一进一出好爽视频 | 亚洲综合伊人久久大杳蕉 | 亚洲 另类 在线 欧美 制服 | 人妻尝试又大又粗久久 | 丰满人妻被黑人猛烈进入 | 国内丰满熟女出轨videos | 成人女人看片免费视频放人 | 亚洲国产成人a精品不卡在线 | 131美女爱做视频 | 成人试看120秒体验区 | 国产成人综合美国十次 | 动漫av网站免费观看 | 亚洲另类伦春色综合小说 | 99精品视频在线观看免费 | 麻豆国产人妻欲求不满谁演的 | 欧美一区二区三区 | 一本精品99久久精品77 | 欧美熟妇另类久久久久久不卡 | 亚洲人成网站在线播放942 | 亚洲国产欧美日韩精品一区二区三区 | 欧美老人巨大xxxx做受 | 国产偷抇久久精品a片69 | 骚片av蜜桃精品一区 | av无码久久久久不卡免费网站 | 熟妇人妻无码xxx视频 | 精品国产麻豆免费人成网站 | 色婷婷综合激情综在线播放 | 国产亚洲精品久久久ai换 | 性啪啪chinese东北女人 | 国产精品va在线观看无码 | 又紧又大又爽精品一区二区 | 国内少妇偷人精品视频 | 亚洲一区二区观看播放 | 精品成在人线av无码免费看 | 国产成人精品久久亚洲高清不卡 | 极品嫩模高潮叫床 | 人人澡人人妻人人爽人人蜜桃 | 亚洲一区二区三区 | 日本精品高清一区二区 | 在线观看国产一区二区三区 | 一本大道久久东京热无码av | 青青草原综合久久大伊人精品 | 亚洲欧美色中文字幕在线 | 亚洲日韩精品欧美一区二区 | 欧洲熟妇精品视频 | 美女黄网站人色视频免费国产 | 一二三四社区在线中文视频 | 无码午夜成人1000部免费视频 | 日韩成人一区二区三区在线观看 | 熟女俱乐部五十路六十路av | 久久aⅴ免费观看 | 小鲜肉自慰网站xnxx | 国产精品va在线观看无码 | 人妻插b视频一区二区三区 | 亚洲啪av永久无码精品放毛片 | 久久精品中文字幕一区 | 亚洲 高清 成人 动漫 | 亚洲国产av美女网站 | 超碰97人人射妻 | 午夜性刺激在线视频免费 | 国产精品爱久久久久久久 | 精品国产成人一区二区三区 | 亚洲欧美国产精品专区久久 | 亚洲精品成人福利网站 | 欧美人妻一区二区三区 | a在线观看免费网站大全 | 特级做a爰片毛片免费69 | 中文无码精品a∨在线观看不卡 | 九九在线中文字幕无码 | 青青草原综合久久大伊人精品 | 高清不卡一区二区三区 | 波多野结衣高清一区二区三区 | 日韩欧美中文字幕在线三区 | 最近免费中文字幕中文高清百度 | 久久午夜无码鲁丝片午夜精品 | 欧美老妇交乱视频在线观看 | 天天拍夜夜添久久精品 | 对白脏话肉麻粗话av | ass日本丰满熟妇pics | 国产性生大片免费观看性 | 国产精品爱久久久久久久 | 少妇性l交大片 | 久久久久免费精品国产 | 日日摸夜夜摸狠狠摸婷婷 | www国产亚洲精品久久网站 | 亚洲国产精品毛片av不卡在线 | 久久精品国产99精品亚洲 | 色诱久久久久综合网ywww | 影音先锋中文字幕无码 | 人人妻人人澡人人爽人人精品 | 啦啦啦www在线观看免费视频 | 天海翼激烈高潮到腰振不止 | 日韩精品成人一区二区三区 | 99精品久久毛片a片 | 国产精品自产拍在线观看 | 天天拍夜夜添久久精品 | 国产精品亚洲五月天高清 | 夜夜躁日日躁狠狠久久av | 真人与拘做受免费视频一 | 国产亚洲人成在线播放 | 欧美丰满老熟妇xxxxx性 | 亚洲成色www久久网站 | а天堂中文在线官网 | 无码任你躁久久久久久久 | 国产av人人夜夜澡人人爽麻豆 | 中文字幕色婷婷在线视频 | 亚洲性无码av中文字幕 | 漂亮人妻洗澡被公强 日日躁 | 无码人妻精品一区二区三区下载 | 国产电影无码午夜在线播放 | 黑人巨大精品欧美黑寡妇 | 丝袜美腿亚洲一区二区 | 日产国产精品亚洲系列 | 美女黄网站人色视频免费国产 | 精品偷拍一区二区三区在线看 | 少女韩国电视剧在线观看完整 | 国产午夜精品一区二区三区嫩草 | 成人无码视频免费播放 | 99国产欧美久久久精品 | 性生交片免费无码看人 | 色综合久久久无码网中文 | 亚洲经典千人经典日产 | 色爱情人网站 | 亚洲另类伦春色综合小说 | 人人妻人人藻人人爽欧美一区 | 久久97精品久久久久久久不卡 | 丰满少妇人妻久久久久久 | 日本一区二区更新不卡 | 97精品人妻一区二区三区香蕉 | 国产精品对白交换视频 | 丰满少妇弄高潮了www | 欧美zoozzooz性欧美 | 久久国产劲爆∧v内射 | 日韩成人一区二区三区在线观看 | 亚洲色大成网站www国产 | 大胆欧美熟妇xx | 久久亚洲a片com人成 | 午夜精品一区二区三区的区别 | 国产两女互慰高潮视频在线观看 | 沈阳熟女露脸对白视频 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 日本一区二区三区免费播放 | 啦啦啦www在线观看免费视频 | 国产免费久久久久久无码 | 六月丁香婷婷色狠狠久久 | 成人无码精品一区二区三区 | 久久久久av无码免费网 | 亚洲色无码一区二区三区 | www成人国产高清内射 | 高潮毛片无遮挡高清免费 | 成人试看120秒体验区 | 亚洲欧美日韩成人高清在线一区 | 国产sm调教视频在线观看 | 中文字幕日产无线码一区 | 88国产精品欧美一区二区三区 | 久9re热视频这里只有精品 | 98国产精品综合一区二区三区 | 国产精品igao视频网 | 狠狠色欧美亚洲狠狠色www | 久久久久国色av免费观看性色 | 久久精品人人做人人综合试看 | 丁香啪啪综合成人亚洲 | 一本久道久久综合狠狠爱 | 老熟妇乱子伦牲交视频 | 国产亚洲精品久久久久久大师 | 国产区女主播在线观看 | 亚洲色大成网站www国产 | 精品久久综合1区2区3区激情 | 天天做天天爱天天爽综合网 | 人人妻人人澡人人爽欧美精品 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 亚洲精品久久久久avwww潮水 | 欧洲熟妇色 欧美 | 蜜臀aⅴ国产精品久久久国产老师 | 中文字幕日产无线码一区 | 国内少妇偷人精品视频免费 | 精品国产国产综合精品 | 伊人久久大香线焦av综合影院 | 国产suv精品一区二区五 | 色欲av亚洲一区无码少妇 | 欧美人与物videos另类 | 国产后入清纯学生妹 | 亚洲一区二区三区四区 | 日本精品久久久久中文字幕 | 久久久婷婷五月亚洲97号色 | 久久综合激激的五月天 | 图片区 小说区 区 亚洲五月 | 久久精品99久久香蕉国产色戒 | 婷婷色婷婷开心五月四房播播 | 亚洲色在线无码国产精品不卡 | 亚洲 欧美 激情 小说 另类 | 人妻少妇精品无码专区动漫 | av香港经典三级级 在线 | 人妻少妇被猛烈进入中文字幕 | 国产成人午夜福利在线播放 | 国产成人午夜福利在线播放 | 人人澡人人透人人爽 | 少妇高潮喷潮久久久影院 | 狠狠躁日日躁夜夜躁2020 | 理论片87福利理论电影 | 色五月五月丁香亚洲综合网 | 久久久久亚洲精品男人的天堂 | 美女黄网站人色视频免费国产 | 欧美精品免费观看二区 | 亚洲精品国产精品乱码不卡 | 麻豆人妻少妇精品无码专区 | 中文字幕亚洲情99在线 | 国产性生交xxxxx无码 | 亚洲中文无码av永久不收费 | 国产精品鲁鲁鲁 | 成人精品一区二区三区中文字幕 | 在线观看国产午夜福利片 | 亚洲日韩av一区二区三区中文 | 国产乱人无码伦av在线a | 色诱久久久久综合网ywww | 丰满人妻翻云覆雨呻吟视频 | 少妇无码一区二区二三区 | 亚洲s码欧洲m码国产av | 熟女俱乐部五十路六十路av | 日韩人妻少妇一区二区三区 | 又大又硬又黄的免费视频 | 亚洲精品国产精品乱码不卡 | 丰满少妇熟乱xxxxx视频 | 免费国产黄网站在线观看 | 日本精品少妇一区二区三区 | 日本一区二区三区免费播放 | 日韩欧美成人免费观看 | 国产亚洲欧美日韩亚洲中文色 | 人人超人人超碰超国产 | 国产精品第一国产精品 | 欧美人与禽猛交狂配 | 久青草影院在线观看国产 | 中文字幕无码免费久久9一区9 | 99riav国产精品视频 | 国产亚洲精品久久久久久 | 国产偷国产偷精品高清尤物 | 乱人伦中文视频在线观看 | 国产麻豆精品精东影业av网站 | 色婷婷香蕉在线一区二区 | 亚洲欧美色中文字幕在线 | 国产无套粉嫩白浆在线 | 男人的天堂2018无码 | 国产真实夫妇视频 | 99麻豆久久久国产精品免费 | 成人三级无码视频在线观看 | 午夜熟女插插xx免费视频 | 国产午夜亚洲精品不卡下载 | 亚洲中文字幕乱码av波多ji | 人人爽人人澡人人人妻 | av无码久久久久不卡免费网站 | 亚洲成av人综合在线观看 | 亚洲熟妇色xxxxx亚洲 | 国产69精品久久久久app下载 | 小sao货水好多真紧h无码视频 | 野外少妇愉情中文字幕 | 国产无套粉嫩白浆在线 | 四虎影视成人永久免费观看视频 | 99re在线播放 | 亚洲人成影院在线观看 | 亚洲午夜无码久久 | 亚洲性无码av中文字幕 | 波多野结衣aⅴ在线 | 伊人色综合久久天天小片 | 999久久久国产精品消防器材 | 成人精品一区二区三区中文字幕 | 色综合天天综合狠狠爱 | 无码av中文字幕免费放 | 爽爽影院免费观看 | 人人妻人人澡人人爽人人精品 | 毛片内射-百度 | 免费无码的av片在线观看 | 东京热一精品无码av | 久久国产精品二国产精品 | 国产精品高潮呻吟av久久 | 精品国产aⅴ无码一区二区 | 捆绑白丝粉色jk震动捧喷白浆 | 嫩b人妻精品一区二区三区 | 国产在线精品一区二区高清不卡 | 国内少妇偷人精品视频 | 国产色在线 | 国产 | 亚洲色大成网站www | 精品少妇爆乳无码av无码专区 | 小泽玛莉亚一区二区视频在线 | 中文字幕无码免费久久9一区9 | 国产精品无套呻吟在线 | 国产人妻大战黑人第1集 | aⅴ亚洲 日韩 色 图网站 播放 | 亚洲精品国产a久久久久久 | 久久久久人妻一区精品色欧美 | 在线观看国产午夜福利片 | 性欧美牲交在线视频 | 无码人妻丰满熟妇区毛片18 | 亚洲第一网站男人都懂 | 亚洲国产成人av在线观看 | 国产特级毛片aaaaaaa高清 | 熟妇人妻无码xxx视频 | 无套内谢老熟女 | 亚洲人成人无码网www国产 | 在线成人www免费观看视频 | 人人妻人人澡人人爽欧美一区九九 | 日韩人妻无码一区二区三区久久99 | 久久久精品国产sm最大网站 | 国产无遮挡吃胸膜奶免费看 | 国产成人久久精品流白浆 | 国产网红无码精品视频 | 中文字幕久久久久人妻 | 宝宝好涨水快流出来免费视频 | 日日噜噜噜噜夜夜爽亚洲精品 | 亚洲娇小与黑人巨大交 | 色妞www精品免费视频 | 天堂久久天堂av色综合 | 97无码免费人妻超级碰碰夜夜 | 无码人妻精品一区二区三区不卡 | 国产色精品久久人妻 | 午夜精品久久久久久久久 | 九一九色国产 | 无码人妻久久一区二区三区不卡 | 色综合天天综合狠狠爱 | 亚洲爆乳大丰满无码专区 | 国产性生交xxxxx无码 | 国产精品.xx视频.xxtv | 日本丰满熟妇videos | 少女韩国电视剧在线观看完整 | 性生交大片免费看l | 中文精品久久久久人妻不卡 | 永久免费精品精品永久-夜色 | 国精产品一品二品国精品69xx | 国产成人亚洲综合无码 | 亚洲区小说区激情区图片区 | 欧美喷潮久久久xxxxx | 国产凸凹视频一区二区 | 久久久久亚洲精品中文字幕 | 成人影院yy111111在线观看 | 亚拍精品一区二区三区探花 | 亚洲日韩av一区二区三区中文 | 欧美日本日韩 | 亚洲欧美综合区丁香五月小说 | 久久久久久久人妻无码中文字幕爆 | 无码av岛国片在线播放 | 国产尤物精品视频 | 国语自产偷拍精品视频偷 | 国产在线无码精品电影网 | 精品亚洲韩国一区二区三区 | 国产在线精品一区二区高清不卡 | 欧美人与物videos另类 | 性色av无码免费一区二区三区 | 成年女人永久免费看片 | 国产又爽又猛又粗的视频a片 | 国语精品一区二区三区 | 激情五月综合色婷婷一区二区 | 荡女精品导航 | 天堂久久天堂av色综合 | 亚洲精品国产a久久久久久 | 水蜜桃av无码 | 少妇性荡欲午夜性开放视频剧场 | 人人妻人人澡人人爽欧美一区九九 | 国产精品美女久久久网av | 亚洲中文字幕无码中文字在线 | 久久人人爽人人爽人人片av高清 | 扒开双腿吃奶呻吟做受视频 | 国产成人无码a区在线观看视频app | 久久久久久a亚洲欧洲av冫 | 日产精品高潮呻吟av久久 | 性欧美大战久久久久久久 | 亚洲欧美日韩成人高清在线一区 | 午夜无码人妻av大片色欲 | 亚洲成av人片在线观看无码不卡 | 国产无遮挡又黄又爽免费视频 | 成人毛片一区二区 | 强辱丰满人妻hd中文字幕 | 国产在线无码精品电影网 | 伊人久久大香线蕉av一区二区 | 性欧美大战久久久久久久 | 欧美午夜特黄aaaaaa片 | 午夜精品一区二区三区在线观看 | 国产成人无码午夜视频在线观看 | 亚洲乱码国产乱码精品精 | 少妇的肉体aa片免费 | 欧美 亚洲 国产 另类 | 伊人久久大香线蕉午夜 | 国产精品资源一区二区 | 无码精品国产va在线观看dvd | 啦啦啦www在线观看免费视频 | 亚洲成a人片在线观看无码3d | 国产又粗又硬又大爽黄老大爷视 | 最新国产乱人伦偷精品免费网站 | 亚洲精品久久久久久一区二区 | www成人国产高清内射 | 十八禁视频网站在线观看 | 欧美一区二区三区视频在线观看 | 精品无码国产一区二区三区av | 国产无遮挡吃胸膜奶免费看 | 国产极品美女高潮无套在线观看 | 成人欧美一区二区三区黑人免费 | 成年美女黄网站色大免费视频 | 激情人妻另类人妻伦 | 成人欧美一区二区三区黑人 | 在线亚洲高清揄拍自拍一品区 | 国产精品久久久久久久影院 | 国产女主播喷水视频在线观看 | 无码一区二区三区在线观看 | 中文字幕无码免费久久99 | 任你躁在线精品免费 | 国产精品久久久 | 无码一区二区三区在线 | 鲁鲁鲁爽爽爽在线视频观看 | 日日碰狠狠躁久久躁蜜桃 | 夜夜躁日日躁狠狠久久av | 国产乡下妇女做爰 | 中文字幕精品av一区二区五区 | 免费国产黄网站在线观看 | 国产极品美女高潮无套在线观看 | 国产精品欧美成人 | 免费看男女做好爽好硬视频 | 性史性农村dvd毛片 | 欧美 日韩 亚洲 在线 | 久久综合狠狠综合久久综合88 | 欧洲熟妇色 欧美 | 蜜臀av无码人妻精品 | 帮老师解开蕾丝奶罩吸乳网站 | 亚洲精品欧美二区三区中文字幕 | 久久久精品成人免费观看 | 中国女人内谢69xxxxxa片 | 丝袜足控一区二区三区 | 国产一区二区三区日韩精品 | 丰满人妻一区二区三区免费视频 | 国产在热线精品视频 | 丰满人妻翻云覆雨呻吟视频 | 免费人成在线视频无码 | 国产成人无码a区在线观看视频app | 久久久久久亚洲精品a片成人 | 精品无码国产一区二区三区av | 久在线观看福利视频 | 久久综合给合久久狠狠狠97色 | 久久亚洲中文字幕精品一区 | 亚洲日韩av片在线观看 | 欧美性色19p | 亚洲人成网站在线播放942 | 中文字幕无码免费久久99 | 少妇无码吹潮 | 精品久久久中文字幕人妻 | 亚洲精品成a人在线观看 | 婷婷六月久久综合丁香 | 久久99精品久久久久婷婷 | 国产午夜精品一区二区三区嫩草 | 捆绑白丝粉色jk震动捧喷白浆 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 欧美丰满熟妇xxxx性ppx人交 | 99久久精品国产一区二区蜜芽 | 无人区乱码一区二区三区 | 成人三级无码视频在线观看 | 高中生自慰www网站 | 国产九九九九九九九a片 | 图片小说视频一区二区 | 夫妻免费无码v看片 | 国产电影无码午夜在线播放 | 国产成人无码区免费内射一片色欲 | 无码成人精品区在线观看 | 丰满妇女强制高潮18xxxx | 人人超人人超碰超国产 | 波多野42部无码喷潮在线 | 天堂а√在线地址中文在线 | 国产农村妇女高潮大叫 | 18黄暴禁片在线观看 | 欧美日韩一区二区综合 | 亚洲日韩av一区二区三区四区 | 午夜理论片yy44880影院 | 色欲久久久天天天综合网精品 | 国产精品.xx视频.xxtv | 精品欧美一区二区三区久久久 | 久久久久久国产精品无码下载 | 亚洲aⅴ无码成人网站国产app | 中国大陆精品视频xxxx | 又黄又爽又色的视频 | 精品厕所偷拍各类美女tp嘘嘘 | 日本又色又爽又黄的a片18禁 | 国产成人无码区免费内射一片色欲 | 伊人久久婷婷五月综合97色 | 国产精品久久久久7777 | 国产一精品一av一免费 | 天天做天天爱天天爽综合网 | 少妇高潮喷潮久久久影院 | 精品厕所偷拍各类美女tp嘘嘘 | 性色欲网站人妻丰满中文久久不卡 | 欧美成人高清在线播放 | 国产精品无套呻吟在线 | 国产成人精品三级麻豆 | 国产成人午夜福利在线播放 | 老司机亚洲精品影院无码 | 久久人人爽人人爽人人片ⅴ | 亚洲色在线无码国产精品不卡 | 精品偷拍一区二区三区在线看 | 窝窝午夜理论片影院 | 最新国产乱人伦偷精品免费网站 | 国产热a欧美热a在线视频 | 小sao货水好多真紧h无码视频 | 日本欧美一区二区三区乱码 | 一本色道久久综合亚洲精品不卡 | 亚洲s色大片在线观看 | 久久综合香蕉国产蜜臀av | 亚洲精品午夜无码电影网 | 日韩欧美群交p片內射中文 | 激情五月综合色婷婷一区二区 | 精品国产aⅴ无码一区二区 | 久久午夜夜伦鲁鲁片无码免费 | 夜夜高潮次次欢爽av女 | 欧美日韩久久久精品a片 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 国内老熟妇对白xxxxhd | 无码纯肉视频在线观看 | 国内老熟妇对白xxxxhd | 国产激情无码一区二区app | 国产亲子乱弄免费视频 | 中文字幕人妻丝袜二区 | 性啪啪chinese东北女人 | 日韩av无码中文无码电影 | 国精产品一品二品国精品69xx | 国产精品第一国产精品 | 国产电影无码午夜在线播放 | 少妇高潮喷潮久久久影院 | 俺去俺来也在线www色官网 | 国产精品久久久久无码av色戒 | 四虎影视成人永久免费观看视频 | 国产精品永久免费视频 | 鲁一鲁av2019在线 | 波多野结衣av在线观看 | 欧美 丝袜 自拍 制服 另类 | 荫蒂添的好舒服视频囗交 | 中文字幕无码免费久久9一区9 | 色婷婷欧美在线播放内射 | 免费观看又污又黄的网站 | 天干天干啦夜天干天2017 | 亚洲人成无码网www | 中文字幕日产无线码一区 | 双乳奶水饱满少妇呻吟 | 亚洲中文字幕在线观看 | 日韩精品无码一本二本三本色 | 亚洲精品久久久久avwww潮水 | 中文字幕乱妇无码av在线 | 日本肉体xxxx裸交 | 一本精品99久久精品77 | 国产精品久久久久久久9999 | 风流少妇按摩来高潮 | 在线观看国产午夜福利片 | 欧美激情内射喷水高潮 | 欧洲vodafone精品性 | 色欲久久久天天天综合网精品 | 久久视频在线观看精品 | 中文字幕无线码免费人妻 | 男女猛烈xx00免费视频试看 | 国产精品久久久久7777 | 国产精品国产三级国产专播 | 亚洲中文字幕久久无码 | 少妇无套内谢久久久久 | 亚洲精品中文字幕乱码 | a片免费视频在线观看 | 国产在线aaa片一区二区99 | 无码福利日韩神码福利片 | 色五月五月丁香亚洲综合网 | 性欧美疯狂xxxxbbbb | 蜜臀av在线观看 在线欧美精品一区二区三区 | av无码久久久久不卡免费网站 | 久久婷婷五月综合色国产香蕉 | 中文字幕人妻丝袜二区 | 久久久久久久人妻无码中文字幕爆 | 国产亚洲tv在线观看 | 性欧美大战久久久久久久 | 久久久久久a亚洲欧洲av冫 | 55夜色66夜色国产精品视频 | 久久久久成人片免费观看蜜芽 | 午夜无码区在线观看 | 日韩av无码一区二区三区不卡 | 天堂а√在线中文在线 | 精品亚洲成av人在线观看 | 中文字幕无码人妻少妇免费 | 内射爽无广熟女亚洲 | 天天躁夜夜躁狠狠是什么心态 | 中文无码伦av中文字幕 | 亚洲日本va午夜在线电影 | 日日碰狠狠躁久久躁蜜桃 | 亚洲色大成网站www | 蜜臀aⅴ国产精品久久久国产老师 | 台湾无码一区二区 | 国产成人无码a区在线观看视频app | 久久成人a毛片免费观看网站 | 丰腴饱满的极品熟妇 | 天天做天天爱天天爽综合网 | 永久免费观看美女裸体的网站 | 精品aⅴ一区二区三区 | 久久久久久久女国产乱让韩 | 精品久久久久久人妻无码中文字幕 | 久久天天躁狠狠躁夜夜免费观看 | 帮老师解开蕾丝奶罩吸乳网站 | 久久久精品成人免费观看 | 久久五月精品中文字幕 | 日本肉体xxxx裸交 | 国产成人精品三级麻豆 | 国产人妻久久精品二区三区老狼 | 久久国产36精品色熟妇 | 国产在线精品一区二区三区直播 | 成在人线av无码免观看麻豆 | 对白脏话肉麻粗话av | 在线 国产 欧美 亚洲 天堂 | 中文字幕无码免费久久99 | 亚洲色大成网站www | 国产精品久久福利网站 | 亚洲精品国产精品乱码视色 | 少妇一晚三次一区二区三区 | 欧美色就是色 | 国产午夜亚洲精品不卡下载 | 初尝人妻少妇中文字幕 | 鲁鲁鲁爽爽爽在线视频观看 | 无套内谢的新婚少妇国语播放 | 久久久久人妻一区精品色欧美 | 乱中年女人伦av三区 | 婷婷色婷婷开心五月四房播播 | 中文字幕亚洲情99在线 | 欧美zoozzooz性欧美 | 丰满少妇熟乱xxxxx视频 | √天堂中文官网8在线 | 午夜福利试看120秒体验区 | 国产精品办公室沙发 | 亚洲 a v无 码免 费 成 人 a v | 精品国产青草久久久久福利 | 亚洲の无码国产の无码步美 | 国产香蕉尹人视频在线 | 久久婷婷五月综合色国产香蕉 | 亚洲欧洲无卡二区视頻 | 欧美精品无码一区二区三区 | 2020久久超碰国产精品最新 | 人人妻在人人 | 中文字幕无线码免费人妻 | 色综合久久久无码中文字幕 | 色欲综合久久中文字幕网 | 国产精品成人av在线观看 | 亚洲成色www久久网站 | 内射后入在线观看一区 | 国产人妻人伦精品 | 黑人巨大精品欧美黑寡妇 | 精品欧洲av无码一区二区三区 | 永久免费观看美女裸体的网站 | 欧美xxxx黑人又粗又长 | 荡女精品导航 | 国产亚洲美女精品久久久2020 | 国产suv精品一区二区五 | 毛片内射-百度 | 色综合久久中文娱乐网 | 300部国产真实乱 | 亚洲国产精品久久人人爱 | 中文字幕日产无线码一区 | 精品国产一区av天美传媒 | 欧美丰满少妇xxxx性 | www一区二区www免费 | 人妻无码αv中文字幕久久琪琪布 | 亚洲s色大片在线观看 | 欧美人与牲动交xxxx | 纯爱无遮挡h肉动漫在线播放 | 粗大的内捧猛烈进出视频 | 麻豆果冻传媒2021精品传媒一区下载 | 久久97精品久久久久久久不卡 | 亚洲高清偷拍一区二区三区 | 久久久精品欧美一区二区免费 | 日本护士xxxxhd少妇 | 国内老熟妇对白xxxxhd | 国产精品igao视频网 | 在线 国产 欧美 亚洲 天堂 | 久久婷婷五月综合色国产香蕉 | 久久久久se色偷偷亚洲精品av | 无码吃奶揉捏奶头高潮视频 | 成人性做爰aaa片免费看不忠 | 人人妻人人澡人人爽欧美一区九九 | 国产一区二区三区精品视频 | 色五月丁香五月综合五月 | 大胆欧美熟妇xx | 扒开双腿吃奶呻吟做受视频 | 国产偷抇久久精品a片69 | 国产精品久久国产精品99 | 青春草在线视频免费观看 | 免费无码的av片在线观看 | 图片小说视频一区二区 | 亚洲成色www久久网站 | 日本一区二区三区免费高清 | 亚洲乱码日产精品bd | 久久久久久久久蜜桃 | 蜜桃视频插满18在线观看 | 欧洲熟妇精品视频 | 丰满少妇弄高潮了www | 亚洲区小说区激情区图片区 | 中文字幕无码av波多野吉衣 | 国产va免费精品观看 | 久久国产精品萌白酱免费 | 九九热爱视频精品 | 日韩精品久久久肉伦网站 | 天天做天天爱天天爽综合网 | 97夜夜澡人人爽人人喊中国片 | av香港经典三级级 在线 | av香港经典三级级 在线 | 美女毛片一区二区三区四区 | 国产成人一区二区三区在线观看 | 最新国产麻豆aⅴ精品无码 | 亚洲 激情 小说 另类 欧美 | 日日干夜夜干 | 最近的中文字幕在线看视频 | 成熟妇人a片免费看网站 | 兔费看少妇性l交大片免费 | 国产激情无码一区二区 | 精品久久久无码人妻字幂 | 成熟女人特级毛片www免费 | 女人被男人爽到呻吟的视频 | 国产综合久久久久鬼色 | 日韩精品成人一区二区三区 | 伊人久久大香线焦av综合影院 | 免费看男女做好爽好硬视频 | 亚洲成熟女人毛毛耸耸多 | 日本精品高清一区二区 | 久久久www成人免费毛片 | 国产卡一卡二卡三 | 欧美日韩色另类综合 | 300部国产真实乱 | 未满成年国产在线观看 | 亚洲第一网站男人都懂 | 丰满人妻被黑人猛烈进入 | 色综合久久久无码中文字幕 | 沈阳熟女露脸对白视频 | 四虎影视成人永久免费观看视频 | 无码人妻丰满熟妇区毛片18 | 久久精品人人做人人综合试看 | 最近的中文字幕在线看视频 | 无码人妻久久一区二区三区不卡 | 日产精品高潮呻吟av久久 | 67194成是人免费无码 | 亚洲中文字幕在线观看 | 亚洲自偷自拍另类第1页 | 思思久久99热只有频精品66 | 国产午夜视频在线观看 | 亚洲色偷偷男人的天堂 | 亚洲中文字幕无码中字 | 99riav国产精品视频 | 大色综合色综合网站 | 午夜成人1000部免费视频 | 欧美丰满熟妇xxxx性ppx人交 | 麻豆md0077饥渴少妇 | 99精品国产综合久久久久五月天 | 国产精品视频免费播放 | 国产精品久久久久久无码 | 人人妻在人人 | 少妇高潮一区二区三区99 | 日日碰狠狠丁香久燥 | 亚洲国产精品无码一区二区三区 | 亚洲人成人无码网www国产 | 欧美老熟妇乱xxxxx | 久久国语露脸国产精品电影 | 国产极品视觉盛宴 | 女人被男人躁得好爽免费视频 | 国产手机在线αⅴ片无码观看 | 亚洲成a人片在线观看无码3d | 久久99久久99精品中文字幕 | 亚洲自偷自偷在线制服 | 久久精品女人天堂av免费观看 | 亚洲大尺度无码无码专区 | 亚洲 激情 小说 另类 欧美 | 免费无码一区二区三区蜜桃大 | 又粗又大又硬又长又爽 | 国产乱人伦av在线无码 | 动漫av一区二区在线观看 | 欧美一区二区三区视频在线观看 | 中国女人内谢69xxxx | 狂野欧美激情性xxxx | 1000部夫妻午夜免费 | 人人妻人人澡人人爽人人精品浪潮 | а√资源新版在线天堂 | 亚洲综合伊人久久大杳蕉 | 中文字幕无线码免费人妻 | 国产麻豆精品一区二区三区v视界 | 成人精品视频一区二区 | 性生交大片免费看女人按摩摩 | 99久久无码一区人妻 | 日本精品高清一区二区 | 美女黄网站人色视频免费国产 | 强奷人妻日本中文字幕 | 久久久无码中文字幕久... | 奇米影视888欧美在线观看 | 2020久久超碰国产精品最新 | 无码吃奶揉捏奶头高潮视频 | 无码毛片视频一区二区本码 | 亚洲色www成人永久网址 | 国产精品无码成人午夜电影 | 午夜熟女插插xx免费视频 | 天堂久久天堂av色综合 | 久久精品人妻少妇一区二区三区 | 国产乱人偷精品人妻a片 | 国产区女主播在线观看 | 一本久久a久久精品亚洲 | 丰满人妻精品国产99aⅴ | 欧美丰满少妇xxxx性 | 亚洲综合无码一区二区三区 | 精品久久久久久亚洲精品 | 国产精品无码永久免费888 | 免费无码一区二区三区蜜桃大 | 18禁黄网站男男禁片免费观看 | 青青草原综合久久大伊人精品 | 99久久精品日本一区二区免费 | 色婷婷综合中文久久一本 | 熟女体下毛毛黑森林 | 精品久久久无码人妻字幂 | 少妇性荡欲午夜性开放视频剧场 | 图片区 小说区 区 亚洲五月 | 亚洲一区二区观看播放 | 日韩精品乱码av一区二区 | 日韩成人一区二区三区在线观看 | 亚洲精品久久久久avwww潮水 | 亚洲无人区午夜福利码高清完整版 | 美女张开腿让人桶 | 国产精品无码一区二区三区不卡 | 露脸叫床粗话东北少妇 | 成熟女人特级毛片www免费 | 少妇激情av一区二区 | 四虎国产精品一区二区 | 国产人妻精品午夜福利免费 | 狠狠cao日日穞夜夜穞av | 欧美zoozzooz性欧美 | 久久 国产 尿 小便 嘘嘘 | 国产一区二区不卡老阿姨 | 国产性生大片免费观看性 | 天天摸天天透天天添 | 国产一区二区三区日韩精品 | 丁香花在线影院观看在线播放 | 国产sm调教视频在线观看 | 日本熟妇人妻xxxxx人hd | 欧美黑人乱大交 | 色欲久久久天天天综合网精品 | 亚洲中文字幕乱码av波多ji | 国产超碰人人爽人人做人人添 | 日韩精品无码免费一区二区三区 | 日韩少妇白浆无码系列 | 日本xxxx色视频在线观看免费 | 国产精品人妻一区二区三区四 | 日本欧美一区二区三区乱码 | 亚洲午夜福利在线观看 | 少妇性l交大片欧洲热妇乱xxx | 丝袜足控一区二区三区 | 精品少妇爆乳无码av无码专区 | 亚洲 另类 在线 欧美 制服 | 在线欧美精品一区二区三区 | 国产情侣作爱视频免费观看 | 粗大的内捧猛烈进出视频 | 极品尤物被啪到呻吟喷水 | 欧美日韩综合一区二区三区 | 婷婷六月久久综合丁香 | 国产精品第一区揄拍无码 | 99久久精品国产一区二区蜜芽 | 国产精品沙发午睡系列 | 欧美日本免费一区二区三区 | 国产又粗又硬又大爽黄老大爷视 | 午夜熟女插插xx免费视频 | 天天躁日日躁狠狠躁免费麻豆 | 亚洲国产一区二区三区在线观看 | 玩弄少妇高潮ⅹxxxyw | 爆乳一区二区三区无码 | 狠狠亚洲超碰狼人久久 | 欧美三级不卡在线观看 | 少妇性l交大片欧洲热妇乱xxx | 福利一区二区三区视频在线观看 | 成人无码视频在线观看网站 | 狠狠色噜噜狠狠狠7777奇米 | 76少妇精品导航 | aa片在线观看视频在线播放 | 国内老熟妇对白xxxxhd | 中文字幕 亚洲精品 第1页 | 国产成人无码a区在线观看视频app | 在线a亚洲视频播放在线观看 | 丰满肥臀大屁股熟妇激情视频 | 日本精品久久久久中文字幕 | 精品人人妻人人澡人人爽人人 | 欧美日韩一区二区综合 | 亚洲日韩av一区二区三区四区 | 成人欧美一区二区三区黑人 | 久久国产精品萌白酱免费 | 日本精品人妻无码免费大全 | 日日鲁鲁鲁夜夜爽爽狠狠 | 呦交小u女精品视频 | 亚洲欧洲日本综合aⅴ在线 | 高潮毛片无遮挡高清免费视频 | 日欧一片内射va在线影院 | 国产又粗又硬又大爽黄老大爷视 | 欧美精品免费观看二区 | √天堂资源地址中文在线 | 日日噜噜噜噜夜夜爽亚洲精品 | 99久久人妻精品免费二区 | 亚洲综合无码久久精品综合 | 国产区女主播在线观看 | 男人的天堂2018无码 | 牲欲强的熟妇农村老妇女 | 亚洲另类伦春色综合小说 | 国产av无码专区亚洲awww | 成 人 免费观看网站 | 成人试看120秒体验区 | 免费看少妇作爱视频 | av无码电影一区二区三区 | 欧美日韩久久久精品a片 | 亚洲色无码一区二区三区 | 任你躁国产自任一区二区三区 | 国产精品.xx视频.xxtv | 一本色道婷婷久久欧美 | 呦交小u女精品视频 | 免费无码肉片在线观看 | 一区二区三区乱码在线 | 欧洲 | 久在线观看福利视频 | 国产精品亚洲lv粉色 | 久久精品成人欧美大片 | 亚洲一区二区三区香蕉 | 99久久久无码国产精品免费 | 成人无码精品1区2区3区免费看 | 性色av无码免费一区二区三区 | 日本护士毛茸茸高潮 | 久久熟妇人妻午夜寂寞影院 | 狠狠躁日日躁夜夜躁2020 | 76少妇精品导航 | www一区二区www免费 | 国产亚洲人成在线播放 | 97夜夜澡人人双人人人喊 | 欧美熟妇另类久久久久久多毛 | 丰满人妻被黑人猛烈进入 | 国产特级毛片aaaaaa高潮流水 | 日本爽爽爽爽爽爽在线观看免 | 最近免费中文字幕中文高清百度 | 久久亚洲中文字幕精品一区 | 麻豆md0077饥渴少妇 | 无码人妻av免费一区二区三区 | 国产人妖乱国产精品人妖 | 国产97在线 | 亚洲 | 国产无套粉嫩白浆在线 | 无码国产乱人伦偷精品视频 | 人妻插b视频一区二区三区 | 人人爽人人爽人人片av亚洲 | 国产色视频一区二区三区 | 国产精品久久久久久亚洲毛片 | 无码人妻黑人中文字幕 | 亚洲成a人片在线观看日本 | 久久久精品456亚洲影院 | 99久久久国产精品无码免费 | 午夜成人1000部免费视频 | 久久人妻内射无码一区三区 | 精品亚洲韩国一区二区三区 | 亚洲国产成人av在线观看 | 一本大道久久东京热无码av | 丰满少妇熟乱xxxxx视频 | 欧美黑人性暴力猛交喷水 | 波多野结衣一区二区三区av免费 | 国产精品人人爽人人做我的可爱 | 丰满少妇高潮惨叫视频 | 欧美日韩一区二区三区自拍 | 思思久久99热只有频精品66 | 久久99热只有频精品8 | 久久午夜无码鲁丝片午夜精品 | 天堂а√在线中文在线 | 露脸叫床粗话东北少妇 | 爽爽影院免费观看 | 国产精品美女久久久久av爽李琼 | 亚洲中文字幕在线观看 | а√资源新版在线天堂 | 久久久精品欧美一区二区免费 | 精品欧洲av无码一区二区三区 | 精品国产一区二区三区四区 | 亚洲国产高清在线观看视频 | 人人妻在人人 | 国产欧美精品一区二区三区 | 欧美日韩亚洲国产精品 | 伊人久久大香线蕉av一区二区 | 国产成人久久精品流白浆 | 中文字幕中文有码在线 | 爆乳一区二区三区无码 | 国产成人无码专区 | 国产三级精品三级男人的天堂 | 成人性做爰aaa片免费看不忠 | 精品人人妻人人澡人人爽人人 | 亚洲综合伊人久久大杳蕉 | 国产精品久久久久无码av色戒 | 无码人妻精品一区二区三区下载 | 玩弄中年熟妇正在播放 | 精品乱码久久久久久久 | 亚洲爆乳大丰满无码专区 | 中文无码精品a∨在线观看不卡 | 国产精品久久久一区二区三区 | 日韩精品无码免费一区二区三区 | 亚洲男女内射在线播放 | 久久99热只有频精品8 | 成人欧美一区二区三区黑人免费 | 免费无码一区二区三区蜜桃大 | 麻豆果冻传媒2021精品传媒一区下载 | 精品国产国产综合精品 | 国产午夜手机精彩视频 | 图片区 小说区 区 亚洲五月 | 中文精品久久久久人妻不卡 | 日本大乳高潮视频在线观看 | 大色综合色综合网站 | 大肉大捧一进一出好爽视频 | 免费无码一区二区三区蜜桃大 | 在线天堂新版最新版在线8 | 日本爽爽爽爽爽爽在线观看免 | 国产内射爽爽大片视频社区在线 | 日日橹狠狠爱欧美视频 | 亚洲 激情 小说 另类 欧美 | 永久免费观看美女裸体的网站 | 欧美 日韩 人妻 高清 中文 | 久久精品国产一区二区三区 | 国产亚洲美女精品久久久2020 | 波多野结衣aⅴ在线 | 欧美人妻一区二区三区 | 日本熟妇乱子伦xxxx | 久久精品国产日本波多野结衣 | 欧美激情内射喷水高潮 | 精品 日韩 国产 欧美 视频 | 午夜无码区在线观看 | 在线观看免费人成视频 | 国产麻豆精品一区二区三区v视界 | 伊人久久大香线蕉亚洲 | 岛国片人妻三上悠亚 | a国产一区二区免费入口 | 国产精品igao视频网 | 性啪啪chinese东北女人 | 中文无码成人免费视频在线观看 | 国内精品久久久久久中文字幕 | 亚洲中文字幕无码一久久区 | 中文字幕无码av激情不卡 | 久久精品国产99久久6动漫 | 97精品国产97久久久久久免费 | 一个人看的www免费视频在线观看 | 免费播放一区二区三区 | 67194成是人免费无码 | 99riav国产精品视频 | 国内精品人妻无码久久久影院 | 国产av人人夜夜澡人人爽麻豆 | 成熟妇人a片免费看网站 | a国产一区二区免费入口 | 国产午夜无码精品免费看 | 黑人巨大精品欧美一区二区 | 成人免费视频视频在线观看 免费 | 初尝人妻少妇中文字幕 | 国产精品久久久久久无码 | 久久精品丝袜高跟鞋 | 亚洲精品www久久久 | 久久久精品欧美一区二区免费 | 99久久久无码国产aaa精品 | 无码人妻出轨黑人中文字幕 | 国产舌乚八伦偷品w中 | 精品成在人线av无码免费看 | 国产精品自产拍在线观看 | 野外少妇愉情中文字幕 | 一本精品99久久精品77 | 任你躁在线精品免费 | 男女超爽视频免费播放 | 中文字幕无码免费久久9一区9 | 国色天香社区在线视频 | 中国女人内谢69xxxx | 欧美日韩视频无码一区二区三 | 国产综合久久久久鬼色 | 中文字幕无码av激情不卡 | 中文字幕乱码中文乱码51精品 | 国产又爽又猛又粗的视频a片 | 鲁鲁鲁爽爽爽在线视频观看 | 免费人成网站视频在线观看 | 色诱久久久久综合网ywww | 精品久久久无码人妻字幂 | 精品成人av一区二区三区 | 波多野结衣一区二区三区av免费 | 亚洲一区二区三区播放 | 国产三级久久久精品麻豆三级 | 国产成人人人97超碰超爽8 | 午夜男女很黄的视频 | 少妇高潮喷潮久久久影院 | 我要看www免费看插插视频 | 日韩 欧美 动漫 国产 制服 | 熟女俱乐部五十路六十路av | 欧美老人巨大xxxx做受 | 自拍偷自拍亚洲精品10p | 人人澡人人妻人人爽人人蜜桃 | 久久熟妇人妻午夜寂寞影院 | 狠狠综合久久久久综合网 | 狠狠综合久久久久综合网 | 国产xxx69麻豆国语对白 | 永久免费观看美女裸体的网站 | 久久99精品国产麻豆蜜芽 | 精品成在人线av无码免费看 | 精品一区二区三区无码免费视频 | 岛国片人妻三上悠亚 | 亚洲国产精品无码久久久久高潮 | 久久国产精品_国产精品 | 欧美性黑人极品hd | 久久天天躁狠狠躁夜夜免费观看 | 亚洲人成影院在线无码按摩店 | 日本丰满熟妇videos | 日日鲁鲁鲁夜夜爽爽狠狠 | 国产小呦泬泬99精品 | 亚洲精品成人福利网站 | 国产绳艺sm调教室论坛 | 亚洲精品中文字幕 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 99麻豆久久久国产精品免费 | 国产av人人夜夜澡人人爽麻豆 | 狠狠综合久久久久综合网 | 性啪啪chinese东北女人 | 久久亚洲国产成人精品性色 | 综合人妻久久一区二区精品 | www一区二区www免费 | 无码人妻丰满熟妇区毛片18 | 国产精品无码久久av | 性做久久久久久久免费看 | 美女扒开屁股让男人桶 | 亚洲精品无码人妻无码 | 精品久久8x国产免费观看 | 亚洲欧美国产精品久久 | 爽爽影院免费观看 | 俺去俺来也www色官网 | 少女韩国电视剧在线观看完整 | 国产人妻人伦精品1国产丝袜 | 亚洲狠狠色丁香婷婷综合 | 国产精品美女久久久 | 久久久久久亚洲精品a片成人 | 爆乳一区二区三区无码 | 精品国产一区av天美传媒 | 性欧美熟妇videofreesex | 国产激情无码一区二区 | 99麻豆久久久国产精品免费 | 四虎国产精品一区二区 | 久久精品中文字幕一区 | 精品一二三区久久aaa片 | 无码国内精品人妻少妇 | 亚洲另类伦春色综合小说 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 麻豆国产人妻欲求不满 | 久久婷婷五月综合色国产香蕉 | 国产女主播喷水视频在线观看 | 亚洲精品综合五月久久小说 | 97夜夜澡人人双人人人喊 | 国产美女精品一区二区三区 | 亚洲自偷精品视频自拍 | 国产欧美熟妇另类久久久 | 少妇无套内谢久久久久 | 久久精品人人做人人综合试看 | 人妻少妇精品无码专区二区 | 久久久久se色偷偷亚洲精品av | 欧美日韩色另类综合 | 亚洲色www成人永久网址 | 国产午夜亚洲精品不卡下载 | 亚洲精品鲁一鲁一区二区三区 | 欧美激情内射喷水高潮 | 老熟女重囗味hdxx69 | 国产在线精品一区二区三区直播 | 高清国产亚洲精品自在久久 | 啦啦啦www在线观看免费视频 | 国产精品嫩草久久久久 | 久久国产精品精品国产色婷婷 | 在线精品国产一区二区三区 | 狠狠色欧美亚洲狠狠色www | 丰满岳乱妇在线观看中字无码 | 国产欧美亚洲精品a | 成年美女黄网站色大免费视频 | 亚洲va中文字幕无码久久不卡 | 搡女人真爽免费视频大全 | 亚洲毛片av日韩av无码 | 欧美日韩亚洲国产精品 | 中文字幕av无码一区二区三区电影 | 亚洲区小说区激情区图片区 | 精品水蜜桃久久久久久久 | 久热国产vs视频在线观看 | 波多野结衣高清一区二区三区 | 亚洲人成影院在线无码按摩店 | 人妻与老人中文字幕 | 99精品久久毛片a片 | 国产精品亚洲五月天高清 | 国内丰满熟女出轨videos | 动漫av一区二区在线观看 | 国产在线精品一区二区三区直播 | 综合网日日天干夜夜久久 | 无码国产色欲xxxxx视频 | 久久99精品国产麻豆 | 色综合久久88色综合天天 | 亚洲第一无码av无码专区 | 少妇人妻大乳在线视频 | 奇米影视888欧美在线观看 | 一本久道高清无码视频 | 少女韩国电视剧在线观看完整 | 久久久www成人免费毛片 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 成人免费无码大片a毛片 | 天天综合网天天综合色 | 久久伊人色av天堂九九小黄鸭 | 欧美精品免费观看二区 | 色欲久久久天天天综合网精品 | a国产一区二区免费入口 | 免费无码午夜福利片69 | 国产办公室秘书无码精品99 | 国语自产偷拍精品视频偷 | 亚洲国产成人a精品不卡在线 | 无码av免费一区二区三区试看 | 国产精品无套呻吟在线 | 亚洲第一网站男人都懂 | 中文字幕乱妇无码av在线 | 最近免费中文字幕中文高清百度 | 亚洲人成网站免费播放 | 永久免费观看美女裸体的网站 | 亚洲一区二区三区四区 | 波多野结衣乳巨码无在线观看 | 一二三四社区在线中文视频 | 免费看少妇作爱视频 | 欧洲美熟女乱又伦 | 国产又爽又黄又刺激的视频 | 成人aaa片一区国产精品 | 国产午夜福利100集发布 | a在线亚洲男人的天堂 | 中文字幕无码热在线视频 | 人人妻人人澡人人爽人人精品 | 少妇人妻av毛片在线看 | 国内少妇偷人精品视频免费 | 国产在线无码精品电影网 | 亚洲人成影院在线无码按摩店 | 中文字幕人妻无码一区二区三区 | 乱人伦人妻中文字幕无码久久网 | 欧美精品无码一区二区三区 | 国产亚洲精品久久久久久大师 | 中文毛片无遮挡高清免费 | 特级做a爰片毛片免费69 | 欧美喷潮久久久xxxxx | 精品偷自拍另类在线观看 | 成在人线av无码免费 | 性做久久久久久久久 | 精品国产av色一区二区深夜久久 | av在线亚洲欧洲日产一区二区 | 无码人妻久久一区二区三区不卡 | 免费国产黄网站在线观看 | 嫩b人妻精品一区二区三区 | 欧美日本精品一区二区三区 | 国产人妖乱国产精品人妖 | 色综合久久久无码中文字幕 | 免费国产成人高清在线观看网站 | 精品成在人线av无码免费看 | 亚洲国产精品一区二区美利坚 | 人妻少妇精品久久 | 日韩人妻系列无码专区 | 亚洲精品成人av在线 | 午夜时刻免费入口 | 亚洲 欧美 激情 小说 另类 | 欧洲欧美人成视频在线 | 欧美xxxxx精品 | 日本欧美一区二区三区乱码 | 奇米影视888欧美在线观看 | 欧美性生交xxxxx久久久 | 亚洲一区二区三区在线观看网站 | 色狠狠av一区二区三区 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 麻豆精品国产精华精华液好用吗 | 国精产品一品二品国精品69xx | 亚洲成a人一区二区三区 | 国产口爆吞精在线视频 | 国精产品一品二品国精品69xx | 亚洲成av人影院在线观看 | 日韩av无码一区二区三区 | 国内精品九九久久久精品 | 亚洲の无码国产の无码影院 | 精品国产青草久久久久福利 | 99久久99久久免费精品蜜桃 | 国产乡下妇女做爰 | 国模大胆一区二区三区 | aa片在线观看视频在线播放 | 亚洲精品欧美二区三区中文字幕 | 人人妻人人澡人人爽欧美一区九九 | www一区二区www免费 | 又色又爽又黄的美女裸体网站 | 天天摸天天透天天添 | 日本大乳高潮视频在线观看 | a片在线免费观看 | 欧美老熟妇乱xxxxx | 香蕉久久久久久av成人 | 精品国产av色一区二区深夜久久 | 亚洲人成网站免费播放 | 波多野结衣aⅴ在线 | 精品国产一区二区三区四区 | 日本护士毛茸茸高潮 | 熟女少妇人妻中文字幕 | 久久久精品人妻久久影视 | 性啪啪chinese东北女人 | 亚洲日韩精品欧美一区二区 | 国产精品手机免费 | 岛国片人妻三上悠亚 | 无码av最新清无码专区吞精 | 欧美人妻一区二区三区 | 国产精品久久福利网站 | 国产精品亚洲а∨无码播放麻豆 | 成熟妇人a片免费看网站 | 十八禁真人啪啪免费网站 | 久久精品人妻少妇一区二区三区 | 久久精品中文字幕一区 | 丝袜足控一区二区三区 | 午夜福利电影 | 欧美色就是色 | 中文字幕+乱码+中文字幕一区 | 中文字幕人成乱码熟女app | 老司机亚洲精品影院无码 | 久久久精品456亚洲影院 | 一二三四在线观看免费视频 | 国产精品a成v人在线播放 | 亚洲中文字幕乱码av波多ji | 国产精品永久免费视频 | 国产尤物精品视频 | 欧美丰满少妇xxxx性 | 国产精品多人p群无码 | 国产午夜精品一区二区三区嫩草 | 乌克兰少妇xxxx做受 | 红桃av一区二区三区在线无码av | 性欧美videos高清精品 | av在线亚洲欧洲日产一区二区 | av无码久久久久不卡免费网站 | 国产疯狂伦交大片 | 少妇激情av一区二区 | 天天燥日日燥 | 久久亚洲中文字幕无码 | 中文字幕精品av一区二区五区 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 久久精品中文字幕一区 | √天堂资源地址中文在线 | 亚洲国产一区二区三区在线观看 | 18禁止看的免费污网站 | 日本大乳高潮视频在线观看 | 日韩精品a片一区二区三区妖精 | ass日本丰满熟妇pics | 又粗又大又硬又长又爽 | 日韩精品成人一区二区三区 | 人妻夜夜爽天天爽三区 | √天堂中文官网8在线 | 人妻少妇精品无码专区二区 | 国产精品永久免费视频 | 亚洲精品一区二区三区大桥未久 | 377p欧洲日本亚洲大胆 | 欧美老妇交乱视频在线观看 | 无码成人精品区在线观看 | 精品人妻人人做人人爽夜夜爽 | 少妇性l交大片 | 亚洲人成网站免费播放 | 狠狠cao日日穞夜夜穞av | 青春草在线视频免费观看 | 在线观看欧美一区二区三区 | 免费中文字幕日韩欧美 | 嫩b人妻精品一区二区三区 | 国产亚洲欧美日韩亚洲中文色 | 久久精品视频在线看15 | 东京热无码av男人的天堂 | 久久午夜夜伦鲁鲁片无码免费 | 国产一区二区三区日韩精品 | 国内精品人妻无码久久久影院 | 大肉大捧一进一出好爽视频 | 精品一区二区三区波多野结衣 | 亚洲精品鲁一鲁一区二区三区 | 久久综合色之久久综合 | 狂野欧美性猛交免费视频 | 在线欧美精品一区二区三区 | 成熟女人特级毛片www免费 | 亚洲热妇无码av在线播放 | 亚洲国产精品一区二区第一页 | 色综合久久久无码网中文 | 国内精品人妻无码久久久影院 | 国产日产欧产精品精品app | 性欧美疯狂xxxxbbbb | 国产在线精品一区二区三区直播 | 一本大道伊人av久久综合 | 欧美精品国产综合久久 | 国产精品人人爽人人做我的可爱 | 国产三级精品三级男人的天堂 | 久久精品人人做人人综合试看 | 久久精品国产一区二区三区肥胖 | 国产亚洲人成在线播放 | 无码精品国产va在线观看dvd | 在线天堂新版最新版在线8 | 88国产精品欧美一区二区三区 | 300部国产真实乱 | 丰满妇女强制高潮18xxxx | 亚洲精品国产精品乱码视色 | 国产精品久久久久7777 | 人人爽人人爽人人片av亚洲 | 久久久精品人妻久久影视 | 美女扒开屁股让男人桶 | 国产内射爽爽大片视频社区在线 | 亚洲欧美综合区丁香五月小说 | 久久午夜无码鲁丝片 | 麻豆av传媒蜜桃天美传媒 | 精品久久久中文字幕人妻 | 精品夜夜澡人妻无码av蜜桃 | 无码午夜成人1000部免费视频 | 天干天干啦夜天干天2017 | 久久99久久99精品中文字幕 | 久久99精品久久久久久动态图 | 又粗又大又硬毛片免费看 | 久青草影院在线观看国产 | 亚洲中文字幕va福利 | 一个人免费观看的www视频 | 成熟妇人a片免费看网站 | 欧美成人午夜精品久久久 | 中文字幕无码热在线视频 | 夜夜影院未满十八勿进 | 人人超人人超碰超国产 | 亚洲国产午夜精品理论片 | 久久久久av无码免费网 | 牛和人交xxxx欧美 | 丝袜人妻一区二区三区 | 久久99热只有频精品8 | 少妇性l交大片 | 帮老师解开蕾丝奶罩吸乳网站 | 色五月丁香五月综合五月 | 色偷偷人人澡人人爽人人模 | 又色又爽又黄的美女裸体网站 | 青青草原综合久久大伊人精品 | аⅴ资源天堂资源库在线 | 日本一卡2卡3卡四卡精品网站 | 波多野结衣av一区二区全免费观看 | 澳门永久av免费网站 | 麻豆国产人妻欲求不满 | 亚拍精品一区二区三区探花 | 国产精品理论片在线观看 | 亚洲国产av精品一区二区蜜芽 | 亚洲欧美日韩国产精品一区二区 | 中文字幕无码免费久久9一区9 | 久久成人a毛片免费观看网站 | 国产乱人伦偷精品视频 | 免费人成在线视频无码 | 欧美大屁股xxxxhd黑色 | 在线观看国产一区二区三区 | 狠狠cao日日穞夜夜穞av | 久久无码中文字幕免费影院蜜桃 | 无码一区二区三区在线观看 | 无码国产激情在线观看 | 亚洲精品无码人妻无码 | 国产熟妇高潮叫床视频播放 | 亚洲高清偷拍一区二区三区 | 亚洲va欧美va天堂v国产综合 | 牲欲强的熟妇农村老妇女 | 一区二区三区乱码在线 | 欧洲 | 国产成人综合在线女婷五月99播放 | 日产精品高潮呻吟av久久 | 精品亚洲韩国一区二区三区 | 在线а√天堂中文官网 | 日本免费一区二区三区最新 | 图片小说视频一区二区 | 给我免费的视频在线观看 | 中文字幕乱码中文乱码51精品 | 人妻人人添人妻人人爱 | 日本精品少妇一区二区三区 | 久久亚洲中文字幕精品一区 | 成人一区二区免费视频 | 亚拍精品一区二区三区探花 | 精品国产av色一区二区深夜久久 | 亚洲色大成网站www国产 | 乱人伦人妻中文字幕无码 | 国产精品亚洲一区二区三区喷水 | 九九久久精品国产免费看小说 | 欧美日本精品一区二区三区 | 成人精品天堂一区二区三区 | 亚洲精品中文字幕 | 熟妇人妻无乱码中文字幕 | 7777奇米四色成人眼影 | av香港经典三级级 在线 | 欧美人与动性行为视频 | 丝袜足控一区二区三区 | 学生妹亚洲一区二区 | 亚洲精品久久久久久一区二区 | 久久综合网欧美色妞网 | 欧美精品国产综合久久 | 影音先锋中文字幕无码 | 天天av天天av天天透 | 玩弄少妇高潮ⅹxxxyw | 久久婷婷五月综合色国产香蕉 | 国产精品无码成人午夜电影 | 人妻天天爽夜夜爽一区二区 | 最近免费中文字幕中文高清百度 | 亚洲成av人影院在线观看 | 久久人人97超碰a片精品 | 久久久久亚洲精品男人的天堂 | 欧美35页视频在线观看 | 国产在线aaa片一区二区99 | 青草视频在线播放 | 无码精品国产va在线观看dvd | 国产免费观看黄av片 | 少女韩国电视剧在线观看完整 | 性欧美牲交xxxxx视频 | 99久久精品日本一区二区免费 | 日本饥渴人妻欲求不满 | 亚洲乱码国产乱码精品精 | 久久久精品成人免费观看 | 免费看少妇作爱视频 | 日日鲁鲁鲁夜夜爽爽狠狠 | 日本www一道久久久免费榴莲 | 久久久久av无码免费网 | 黑人巨大精品欧美一区二区 | 51国偷自产一区二区三区 | 欧美人妻一区二区三区 | 精品欧洲av无码一区二区三区 | 少女韩国电视剧在线观看完整 | 天天拍夜夜添久久精品 | 日本一区二区三区免费播放 | 日本肉体xxxx裸交 | 亚洲色在线无码国产精品不卡 | 小sao货水好多真紧h无码视频 | 久久综合香蕉国产蜜臀av | 亚洲一区二区三区 | 欧美亚洲国产一区二区三区 | 国产精品久久久久无码av色戒 | 日本乱人伦片中文三区 | 99久久久国产精品无码免费 | 国产午夜亚洲精品不卡下载 | 亚洲日韩精品欧美一区二区 | 久久久久99精品国产片 | 狠狠综合久久久久综合网 | 亚洲欧美国产精品久久 | 精品国产成人一区二区三区 | 男人和女人高潮免费网站 | 国产精品无码成人午夜电影 | 国产午夜亚洲精品不卡 | 一个人看的www免费视频在线观看 | 中文字幕人妻无码一夲道 | 麻豆果冻传媒2021精品传媒一区下载 | 好男人社区资源 | 欧美一区二区三区视频在线观看 | 成人无码视频免费播放 | 国产极品美女高潮无套在线观看 | 性生交大片免费看女人按摩摩 | 国产无套粉嫩白浆在线 | 搡女人真爽免费视频大全 | 成人试看120秒体验区 | 国产乱人偷精品人妻a片 | 久久国产精品精品国产色婷婷 | 成人一在线视频日韩国产 | 国产熟女一区二区三区四区五区 | 99国产精品白浆在线观看免费 | 99久久精品无码一区二区毛片 | 国产国产精品人在线视 | 久久精品国产精品国产精品污 | 蜜桃臀无码内射一区二区三区 | 午夜性刺激在线视频免费 | 狠狠综合久久久久综合网 | 久久综合网欧美色妞网 | 亚洲精品欧美二区三区中文字幕 | 亚洲人成网站色7799 | 国产口爆吞精在线视频 | 国产午夜手机精彩视频 | 久久精品国产精品国产精品污 | 久久精品人人做人人综合试看 | 国产小呦泬泬99精品 | 国产真人无遮挡作爱免费视频 | 西西人体www44rt大胆高清 | 亚洲成av人综合在线观看 | 精品欧洲av无码一区二区三区 | 精品国偷自产在线视频 | 色欲av亚洲一区无码少妇 | 亚洲一区二区三区偷拍女厕 | 国产亚洲精品久久久久久久久动漫 | 亚洲 激情 小说 另类 欧美 | 高清国产亚洲精品自在久久 | 思思久久99热只有频精品66 | 久久久久久亚洲精品a片成人 | 高中生自慰www网站 | 成人精品一区二区三区中文字幕 | 亚洲日韩乱码中文无码蜜桃臀网站 | 国产 浪潮av性色四虎 | 日韩亚洲欧美中文高清在线 | 久久久久久av无码免费看大片 | 精品国产一区av天美传媒 | 在线观看国产午夜福利片 | 久久国产自偷自偷免费一区调 | 国产麻豆精品精东影业av网站 | 中文字幕av无码一区二区三区电影 | 国精品人妻无码一区二区三区蜜柚 | 日本欧美一区二区三区乱码 | 国产精品无码成人午夜电影 | 国产97在线 | 亚洲 | 亚洲日韩乱码中文无码蜜桃臀网站 | 天堂久久天堂av色综合 | 鲁鲁鲁爽爽爽在线视频观看 | 色窝窝无码一区二区三区色欲 | 精品一区二区不卡无码av | 久久午夜无码鲁丝片秋霞 | 夜先锋av资源网站 | 熟妇人妻无码xxx视频 | 欧美老妇交乱视频在线观看 | 一区二区传媒有限公司 | 高清不卡一区二区三区 | 久久精品丝袜高跟鞋 | 久久久久久av无码免费看大片 | 亚洲一区二区三区在线观看网站 | 久久人人爽人人爽人人片ⅴ | 国产精品资源一区二区 | 国产黄在线观看免费观看不卡 | 久久精品中文字幕大胸 | 色综合久久久久综合一本到桃花网 | 色综合久久久无码网中文 | 国产亚洲精品久久久闺蜜 | 亚洲一区二区三区偷拍女厕 | 永久免费观看国产裸体美女 | 亚洲无人区一区二区三区 | 真人与拘做受免费视频一 | 国产精品欧美成人 | 日韩 欧美 动漫 国产 制服 | www一区二区www免费 | 少妇人妻偷人精品无码视频 | 久青草影院在线观看国产 | 国产精品无套呻吟在线 | 牛和人交xxxx欧美 | 俄罗斯老熟妇色xxxx | 少妇的肉体aa片免费 | 亚洲人亚洲人成电影网站色 | 国产成人无码av片在线观看不卡 | 欧美日韩人成综合在线播放 | 久久99精品久久久久久动态图 | 午夜福利一区二区三区在线观看 | 久久久久亚洲精品男人的天堂 | 内射后入在线观看一区 | 久久久久国色av免费观看性色 | 亚洲一区二区三区无码久久 | 自拍偷自拍亚洲精品10p | 六十路熟妇乱子伦 | 色综合久久88色综合天天 | 内射后入在线观看一区 | 7777奇米四色成人眼影 | 色妞www精品免费视频 | 欧美人与物videos另类 | 国产精品99久久精品爆乳 | 欧美成人家庭影院 | 欧美性黑人极品hd | 国产精品鲁鲁鲁 | 国产黑色丝袜在线播放 | 图片小说视频一区二区 | 欧美zoozzooz性欧美 | 永久免费精品精品永久-夜色 | 日韩精品一区二区av在线 | 国产精品对白交换视频 |