成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL MVCC實現原理

數據庫 其他數據庫
這里介紹了 MVCC 在 READ COMMITTD 、 REPEATABLE READ 這兩種隔離級別的事務在執行快照讀操作時訪問記錄的版本鏈的過程。這樣使不同事務的 讀-寫 、 寫-讀 操作并發執行,從而提升系統性能。

1、概念

MVCC (Multiversion Concurrency Control),多版本并發控制。顧名思義,MVCC是通過數據行的多個版本管理實現數據庫的并發控制。這項技術使得在InnoDB的事務隔離級別下執行一致性讀操作有了保證。換言之,就是為了查詢一些正在被另一個事務更新的行,并且可以看到它們被更新之前的值,這樣在做查詢的時候就不用等待另一個事務釋放鎖。

MVCC沒有正式的標準,在不同的DBMS中MVCC的實現方式可能是不同的,也不是普遍使用的。本文講解InnoDB中MVCC的實現機制(MySQL其它的存儲引擎并不支持它)。

2、快照讀和當前讀

MVCC在MySQL InnoDB中的實現主要是為了提高數據庫并發性能,用更好的方式去處理讀-寫沖突,做到即使有讀寫沖突時,也能做到不加鎖,非阻塞并發讀,而這個讀指的就是快照讀,而非當前讀。當前讀實際上是一種加鎖的操作,是悲觀鎖的實現。而MVCC本質是采用樂觀鎖思想的一種方式。

2.1 快照讀

快照讀又叫一致性讀,讀取的是快照數據。不加鎖的簡單的SELECT都屬于快照讀,即不加鎖的非阻塞讀。比如這樣:

SELECT * FROM player WHERE ...

之所以出現快照讀的情況,是基于提高并發性能的考慮,快照讀的實現是基于MVCC,它在很多情況下,避免了加鎖操作,降低了開銷。既然是基于多版本,那么快照讀可能讀到的并不一定是數據的最新版本,而有可能是之前的歷史版本。快照讀的前提是隔離級別不是串行級別,串行級別下的快照讀會退化成當前讀。

2.2 當前讀

當前讀讀取的是記錄的最新版本(最新數據,而不是歷史版本的數據),讀取時還要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。加鎖的SELECT,或者對數據進行增刪改都會進行當前讀。比如:

SELECT * FROM student LOCK IN SHARE MODE; #共享鎖
SELECT * FROM student FOR UPDATE; #排他鎖
INSERT INTO student values ... #排他鎖
DELETE FROM student WHERE ... #排他鎖
UPDATE student SET ... #排他鎖

3、MVCC實現

3.1 隱藏字段

對于使用InnoDB存儲引擎的表來說,它的聚簇索引記錄中都包含兩個必要的隱藏列。

trx_id:每次一個事務對某條聚簇索引記錄進行改動時,都會把該事務的事務id賦值給trx_id隱藏列。

roll_pointer:每次對某條聚簇索引記錄進行改動時,都會把舊的版本寫入到undo日志中,然后這個隱藏列就相當于一個指針,可以通過它來找到該記錄修改前的信息。

3.2 Undo Log版本鏈

舉例: student表數據如下

SELECT * FROM student ;
/*
+----+--------+--------+
| id | name | class |
+----+--------+--------+
| 1 | 張三 | 一班 |
+----+--------+--------+
1 row in set (0.07 sec)
*/

假設插入該記錄的事務id為8,那么此刻該條記錄的示意圖如下所示:

圖片

insert undo只在事務回滾時起作用,當事務提交后,該類型的undo日志就沒用了,它占用的Undo Log Segment也會被系統回收(也就是該undo日志占用的Undo頁面鏈表要么被重用,要么被釋放)。

假設之后兩個事務id分別為10、20的事務對這條記錄進行UPDATE 操作,操作流程如下:

發生時間順序

事務10

事務20

1

BEGIN;


2


BEGIN;

3

UPDATE student SET name=“李四” WHERE id=1;


4

UPDATE student SET name=“王五” WHERE id=1;


5

COMMIT;


6


UPDATE student SET name=“錢七” WHERE id=1;

7


UPDATE student SET name=“宋八” WHERE id=1;

8


COMMIT;

能不能在兩個事務中交叉更新同一條記錄呢?

不能!這就是一個事務修改了另一個未提交事務修改過的數據,臟寫。

InnoDB使用鎖來保證不會有臟寫情況的發生,也就是在第一個事務更新了某條記錄后,就會給這條記錄加鎖,另一個事務再次更新時就需要等待第一個事務提交了,把鎖釋放之后才可以繼續更新。

每次對記錄進行改動,都會記錄一條undo日志,每條undo日志也都有一個roll_pointer屬性(INSERT操作對應的undo日志沒有該屬性,因為該記錄并沒有更早的版本),可以將這些undo日志都連起來,串成一個鏈表:

圖片

對該記錄每次更新后,都會將舊值放到一條undo日志中,就算是該記錄的一個舊版本,隨著更新次數的增多,所有的版本都會被roll_pointer屬性連接成一個鏈表,把這個鏈表稱之為版本鏈,版本鏈的頭節點就是當前記錄最新的值。每個版本中還包含生成該版本時對應的事務id。

3.3 ReadView

在MVCC機制中,多個事務對同一個行記錄進行更新會產生多個歷史快照,這些歷史快照保存在Undo Log里。如果一個事務想要查詢這個行記錄,需要讀取哪個版本的行記錄呢?這時就需要用到ReadView了,它幫我們解決了行的可見性問題。

ReadView就是事務在使用MVCC機制進行快照讀操作時產生的讀視圖。當事務啟動時,會生成數據庫系統當前的一個快照,InnoDB為每個事務構造了一個數組,用來記錄并維護系統當前活躍事務的ID(“活躍”指的就是,啟動了但還沒提交)。

3.3.1 設計思路

使用READ UNCONNMITTED隔離級別的事務,由于可以讀到未提交事務修改過的記錄,所以直接讀取記錄的最新版本就好了。

使用SERIALIZABLE隔離級別的事務,InnoDB規定使用加鎖的方式來訪問記錄。

使用 READ COMMITTED 和 REPEATABLE READ 隔離級別的事務,都必須保證讀到 已經提交了的 事務修改過的記錄。假如另一個事務已經修改了記錄但是尚未提交,是不能直接讀取最新版本的記錄的,核心問題就是需要判斷一下版本鏈中的哪個版本是當前事務可見的,這是ReadView要解決的主要問題。

這個ReadView中主要包含4個比較重要的內容,分別如下:

creator_trx_id ,創建這個 Read View 的事務 ID。

說明:只有在對表中的記錄做改動時(執行INSERT、DELETE、UPDATE這些語句時)才會為事務分配事務id,否則在一個只讀事務中的事務id值都默認為0。

trx_ids ,表示在生成ReadView時當前系統中活躍的讀寫事務的事務id列表 。

up_limit_id ,活躍的事務中最小的事務 ID。

low_limit_id ,表示生成ReadView時系統中應該分配給下一個事務的 id 值。low_limit_id 是系統最大的事務id值,這里要注意是系統中的事務id,需要區別于正在活躍的事務ID。

注意:low_limit_id并不是trx_ids中的最大值,事務id是遞增分配的。比如,現在有id為1,2,3這三個事務,之后id為3的事務提交了。那么一個新的讀事務在生成ReadView時,trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4。

舉例:

trx_ids為trx2、trx3、trx5和trx8的集合,系統的最大事務ID (low_limit_id)為trx8+1(如果之前沒有其他的新增事務),活躍的最小事務ID (up_limit_id)為trx2。

圖片

3.3.2 ReadView的規則

有了這個ReadView,這樣在訪問某條記錄時,只需要按照下邊的步驟判斷記錄的某個版本是否可見。

如果被訪問版本的trx_id屬性值與ReadView中的 creator_trx_id 值相同,意味著當前事務在訪問它自己修改過的記錄,所以該版本可以被當前事務訪問。20可以訪問自己

如果被訪問版本的trx_id屬性值小于ReadView中的 up_limit_id值,表明生成該版本的事務在當前事務生成ReadView前已經提交,所以該版本可以被當前事務訪問。

如果被訪問版本的trx_id屬性值大于或等于ReadView中的 low_limit_id值,表明生成該版本的事務在當前事務生成ReadView后才開啟,所以該版本不可以被當前事務訪問。

如果被訪問版本的trx_id屬性值在ReadView的 up_limit_id 和 low_limit_id之間,那就需要判斷一下trx_id屬性值是不是在 trx_ids 列表中。

如果在,說明創建ReadView時生成該版本的事務還是活躍的,該版本不可以被訪問。

如果不在,說明創建ReadView時生成該版本的事務已經被提交,該版本可以被訪問。

3.4 MVCC整體操作流程

了解了這些概念之后,來看下當查詢一條記錄的時候,系統如何通過MVCC找到它:

首先獲取事務自己的版本號,也就是事務 ID;

獲取 ReadView;

查詢得到的數據,然后與 ReadView 中的事務版本號進行比較;

如果不符合 ReadView 規則,就需要從 Undo Log 中獲取歷史快照;

最后返回符合規則的數據。

如果某個版本的數據對當前事務不可見的話,那就順著版本鏈找到下一個版本的數據,繼續按照上邊的步驟判斷可見性,依此類推,直到版本鏈中的最后一個版本。如果最后一個版本也不可見的話,那么就意味著該條記錄對該事務完全不可見,查詢結果就不包含該記錄。InnoDB中,MVCC是通過Undo Log + Read View進行數據讀取,Undo Log保存了歷史快照,而Read View規則幫我們判斷當前版本的數據是否可見。在隔離級別為讀已提交(Read Committed)時,一個事務中的每一次 SELECT 查詢都會重新獲取一次Read View。

如表所示:

事務

說明

begin;


select * from student where id >2;

獲取一次Read View


select * from student where id >2;

獲取一次Read View

commit;


注意,此時同樣的查詢語句都會重新獲取一次Read View,這時如果Read View 不同,就可能產生不可重復讀或者幻讀的情況。

當隔離級別為可重復讀的時候,就避免了不可重復讀,這是因為一個事務只在第一次SELECT的時候會獲取一次Read View,而后面所有的SELECT都會復用這個Read View,如下表所示:

事務

說明

begin;


select * from student where id >2;

獲取一次Read View


select * from student where id >2;


commit;


4、MVCC示例

假設現在student表中只有一條由事務id為8的事務插入的一條記錄:



SELECT * FROM student ;
/*
+----+--------+--------+
| id | name | class |
+----+--------+--------+
| 1 | 張三 | 一班 |
+----+--------+--------+
1 row in set (0.07 sec)
*/

MVCC只能在READ COMMITTED和REPEATABLE READ兩個隔離級別下工作。READ COMMITTED和REPEATABLE READ生成ReadView的時機是不同的。

4.1 READ COMMITTED隔離級別


READ COMMITTED :每次讀取數據前都生成一個ReadView
現在有兩個 事務id 分別為 10 、 20 的事務在執行
# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;

# Transaction 20
BEGIN;
# 更新了一些別的表的記錄(為了分配事務id)
...

說明:事務執行過程中,只有在第一次真正修改記錄時(比如使用INSERT、DELETE、UPDATE語句),才會被分配一個單獨的事務id,這個事務id是遞增的。所以我們才在事務20中更新一些別的表的記錄,目的是讓它分配事務id。

此刻,表student 中id為1的記錄得到的版本鏈表如下所示:

圖片

假設現在有一個使用 READ COMMITTED 隔離級別的事務開始執行:



# 使用READ COMMITTED隔離級別的事務

BEGIN;
# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值為'張三'

這個·SELECT1·的執行過程如下:

步驟1:在執行SELECT語句時會先生成一個ReadView ,ReadView的trx_ids列表的內容就是[10,20],up_limit_id為10, low_limit_id為21, creator_trx_id為0。

步驟2:從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內容是’王五’,該版本的trx_id值為10,在trx_ids列表內,所以不符合可見性要求,根據roll_pointer跳到下一個版本

步驟3:下一個版本的列name的內容是’李四’,該版本的trx_id值也為10,也在trx_ids列表內,所以也不符合要求,繼續跳到下一個版本

步驟4:下一個版本的列name的內容是’張三’,該版本的trx_id值為8,小于ReadView中的up_limit_id值10,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列name為‘張三’的記錄

之后,把 事務id 為 10 的事務提交一下:

# Transaction 10
BEGIN;

UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;

COMMIT;

然后再到 事務id 為 20 的事務中更新一下表 student 中 id 為 1 的記錄:

# Transaction 20
BEGIN;
# 更新了一些別的表的記錄
...
UPDATE student SET name="錢七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student中 id 為 1 的記錄的版本鏈就長這樣:

圖片

然后再到剛才使用 READ COMMITTED 隔離級別的事務中繼續查找這個 id 為 1 的記錄,如下:

# 使用READ COMMITTED隔離級別的事務
BEGIN;

# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值為'張三'

# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值為'王五'

這個SELECT2的執行過程如下:

步驟1∶在執行SELECT語句時會又會單獨生成一個ReadView,該ReadView的trx_ids列表的內容就是[20],up_limit_id為20,low_limit_id為21, creator_trx_id為0。

步驟2:從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內容是’宋八’,該版本的trx_id值為20,在trx_ids列表內,所以不符合可見性要求,根據roll_pointer跳到下一個版本。

步驟3∶下一個版本的列name的內容是‘錢七’,該版本的trx_id值為20,也在trx_ids列表內,所以也不符合要求,繼續跳到下一個版本

步驟4∶下一個版本的列name的內容是’王五’,該版本的trx_id值為10,小于ReadView中的up_limit_id值20,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列name為’王五’的記錄。

以此類推,如果之后事務id為20的記錄也提交了,再次在使用READ COMMITED 隔離級別的事務查詢表student中id值為1的記錄時,得到的結果就是'宋八'了,具體流程我們就不分析了。

強調:使用READ COMMITTED隔離級別的事務在每次查詢開始時都會生成一個獨立的ReadView

4.2 REPEATABLE READ隔離級別

使用 REPEATABLE READ 隔離級別的事務來說,只會在第一次執行查詢語句時生成一個 ReadView ,之后的查詢就不會重復生成了。

比如,系統里有兩個 事務id 分別為 10 、 20 的事務在執行:



# Transaction 10
BEGIN;
UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;

# Transaction 20
BEGIN;
# 更新了一些別的表的記錄
...

此刻,表student 中 id 為 1 的記錄得到的版本鏈表如下所示:

圖片

假設現在有一個使用 REPEATABLE READ 隔離級別的事務開始執行:


# 使用REPEATABLE READ隔離級別的事務
BEGIN;

# SELECT1:Transaction 10、20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值為'張三'

這個SELECT1的執行過程如下:

步驟1:在執行·SELECT·語句時會先生成一個ReadView,ReadView的trx_ids列表的內容就是[10,20],up_limit_id為10, low_limit_id為21, creator_trx_id為0。

步驟2:然后從版本鏈中挑選可見的記錄,從圖中看出,最新版本的列name的內容是’王五’,該版本的trx_id值為10,在trx_ids列表內,所以不符合可見性要求,根據roll_pointer跳到下一個版本。

步驟3:下一個版本的列name的內容是’李四’,該版本的trx_id值也為10,也在trx_ids列表內,所以也不符合要求,繼續跳到下一個版本。

步驟4∶下一個版本的列name的內容是’張三’,該版本的trx_id值為8,小于ReadView中的up_limit_id值10,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列name為’張三’的記錄

之后,我們把事務id為10的事務提交一下,就像這樣:


# Transaction 10
BEGIN;

UPDATE student SET name="李四" WHERE id=1;
UPDATE student SET name="王五" WHERE id=1;

COMMIT;

然后再到 事務id 為 20 的事務中更新一下表 student 中 id 為 1 的記錄:

# Transaction 20
BEGIN;

# 更新了一些別的表的記錄
...
UPDATE student SET name="錢七" WHERE id=1;
UPDATE student SET name="宋八" WHERE id=1;

此刻,表student 中 id 為 1 的記錄的版本鏈長這樣:

圖片

然后再到剛才使用 REPEATABLE READ 隔離級別的事務中繼續查找這個id 為 1 的記錄,如下:


# 使用REPEATABLE READ隔離級別的事務
BEGIN;

# SELECT1:Transaction 10、20均未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值為'張三'

# SELECT2:Transaction 10提交,Transaction 20未提交
SELECT * FROM student WHERE id = 1; # 得到的列name的值仍為'張三'

SELECT2的執行過程如下:

步驟1:因為當前事務的隔離級別為REPEATABLE READ,而之前在執行SELECT1時已經生成過ReadView了,所以此時直接復用之前的ReadView,之前的ReadView的trx_ids列表的內容就是[10,20],up_limit_id為10,low_limit_id為21, creator_trx_id為0。

步驟2:然后從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列name的內容是’宋八’,該版本的trx_id值為20,在trx_ids列表內,所以不符合可見性要求,根據roll_pointer跳到下一個版本

步驟3:下一個版本的列name的內容是’錢七’,該版本的trx_id值為20,也在trx_ids列表內,所以也不符合要求,繼續跳到下一個版本

步驟4∶下一個版本的列name的內容是’王五’,該版本的trx_id值為10,而trx_ids列表中是包含值為10的事務id的,所以該版本也不符合要求,同理下一個列name的內容是‘李四’的版本也不符合要求。繼續跳到下一個版本

步驟5:下一個版本的列name的內容是’張三’,該版本的trx_id值為8,小于ReadView中的up_limit_id值10,所以這個版本是符合要求的,最后返回給用戶的版本就是這條列c為‘張三’的記錄。

兩次SELECT查詢得到的結果是重復的,記錄的列c值都是‘張三',這就是可重復讀的含義。如果我們之后再把事務id為20的記錄提交了,然后再到剛才使用REPEATABLE READ隔離級別的事務中繼續查找這個id為1的記得到的結果還是‘張三'。

4.3 如何解決幻讀

接下來說明InnoDB 是如何解決幻讀的。

假設現在表 student 中只有一條數據,數據內容中,主鍵 id=1,隱藏的 trx_id=10,它的 undo log 如下圖所示。

圖片

假設現在有事務 A 和事務 B 并發執行, 事務 A 的事務 id 為 20 , 事務 B 的事務 id 為 30 。

步驟1:事務 A 開始第一次查詢數據,查詢的 SQL 語句如下

select * from student where id >= 1;

在開始查詢之前,MySQL 會為事務 A 產生一個 ReadView,此時 ReadView 的內容如下:trx_ids=[20,30] up_limit_id=20 , low_limit_id=31 , creator_trx_id=20 。

由于此時表 student 中只有一條數據,且符合 where id>=1 條件,因此會查詢出來。然后根據 ReadView機制,發現該行數據的trx_id=10,小于事務 A 的 ReadView 里 up_limit_id,這表示這條數據是事務 A 開啟之前,其他事務就已經提交了的數據,因此事務 A 可以讀取到。

結論:事務 A 的第一次查詢,能讀取到一條數據,id=1。

步驟2:接著事務 B(trx_id=30),往表 student 中新插入兩條數據,并提交事務

insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');

此時表student 中就有三條數據了,對應的 undo 如下圖所示:

圖片

步驟3:接著事務 A 開啟第二次查詢,根據可重復讀隔離級別的規則,此時事務 A 并不會再重新生成ReadView。此時表 student 中的 3 條數據都滿足 where id>=1 的條件,因此會先查出來。然后根據ReadView 機制,判斷每條數據是不是都可以被事務 A 看到。

1)首先 id=1 的這條數據,前面已經說過了,可以被事務 A 看到。

2)然后是 id=2 的數據,它的 trx_id=30,此時事務 A 發現,這個值處于 up_limit_id 和 low_limit_id 之間,因此還需要再判斷 30 是否處于 trx_ids 數組內。由于事務 A 的 trx_ids=[20,30],因此在數組內,這表示 id=2 的這條數據是與事務 A 在同一時刻啟動的其他事務提交的,所以這條數據不能讓事務 A 看到

3)同理,id=3 的這條數據,trx_id 也為 30,因此也不能被事務 A 看見

圖片

結論:最終事務 A 的第二次查詢,只能查詢出 id=1 的這條數據。這和事務 A 的第一次查詢的結果是一樣的,因此沒有出現幻讀現象,所以說在 MySQL 的可重復讀隔離級別下,不存在幻讀問題。

5、總結

這里介紹了 MVCC 在 READ COMMITTD 、 REPEATABLE READ 這兩種隔離級別的事務在執行快照讀操作時訪問記錄的版本鏈的過程。這樣使不同事務的 讀-寫 、 寫-讀 操作并發執行,從而提升系統性能。

核心點在于 ReadView 的原理, READ COMMITTD 、 REPEATABLE READ 這兩個隔離級別的一個很大不同就是生成ReadView的時機不同:

  • READ COMMITTD 在每一次進行普通SELECT操作前都會生成一個ReadView
  • REPEATABLE READ只在第一次進行普通SELECT操作前生成一個ReadView,之后的查詢操作都重復使用這個ReadView就好了

說明:之前說執行DELETE語句或者更新主鍵的UPDATE語句并不會立即把對應的記錄完全從頁面中刪除,而是執行一個所謂的delete mark操作,相當于只是對記錄打上了一個刪除標志位,這主要就是為MVCC服務的。

通過MVCC 可以解決:

  • 讀寫之間阻塞的問題。通過MVCC 可以讓讀寫互相不阻塞,即讀不阻塞寫,寫不阻塞讀,這樣就可以提升事務并發處理能力
  • 降低了死鎖的概率。這是因為MVCC采用了樂觀鎖的方式,讀取數據時并不需要加鎖,對于寫操作,也只鎖定必要的行
  • 解決快照讀的問題。當查詢數據庫在某個時間點的快照時,只能看到這個時間點之前事務提交更新的結果,而不能看到這個時間點之后事務提交的更新結果
責任編輯:武曉燕 來源: 得物技術
相關推薦

2025-05-27 01:00:00

2024-11-01 05:10:00

2021-04-06 06:23:18

MVCC并發事務

2021-11-04 08:16:50

MySQL SQL 語句數據庫

2017-05-16 08:59:16

MVCCMYSQL樂觀鎖

2020-10-13 10:32:24

MySQL事務MVCC

2024-12-23 13:00:00

MySQLMVCC數據庫

2024-11-19 15:13:02

2023-12-27 18:16:39

MVCC隔離級別幻讀

2021-10-07 20:12:03

MVCC事務原理

2024-01-09 12:06:55

MVCC并發控制MySQL

2024-10-07 10:02:28

2009-03-26 13:43:59

實現Order ByMySQL

2018-08-20 16:00:23

MySQL并發控制MVCC

2025-01-13 13:12:54

2009-04-02 10:23:13

實現JoinMySQL

2009-03-25 09:00:11

Group By排序MySQL

2021-07-08 07:08:21

MySQL ACID 數據庫

2023-12-06 08:23:16

MVCCmysql

2025-04-28 09:27:26

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产高清一二三区 | 国内自拍偷拍 | 欧美电影在线观看网站 | 综合久久综合久久 | 久久天天综合 | 国产在线一区二区三区 | 国产精品亚洲精品久久 | 人人看人人射 | 国产成人精品网站 | 国产免费看 | 久久大| 四虎成人免费视频 | 日韩欧美国产精品一区二区 | 精品一区免费 | 成人精品一区二区 | 欧洲一级黄 | 日韩不卡三区 | 色综合成人网 | 狠狠操操 | 一区二区三区高清 | 午夜视频免费在线观看 | 久久午夜精品福利一区二区 | 成人福利在线 | 国产精品a久久久久 | 国产欧美日韩精品在线观看 | 黄色网址在线免费观看 | 伊人操| 欧美全黄 | 91精品国产综合久久婷婷香蕉 | 日日夜夜天天 | 一区精品视频 | 国产精品美女久久久久久不卡 | 久久久久久国产精品三区 | 国产精品美女www爽爽爽视频 | 激情网站在线观看 | 人人人干| 午夜寂寞影院在线观看 | 久久久久亚洲精品 | 国产一区成人 | 日韩精品久久久久 | 久久av网站|