精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

InnoDB數據存儲及事務兩階段提交原理解析

數據庫 MySQL
本文詳細介紹MySQL InnoDB的數據模型、數據持久化策略、事務提交以及故障恢復原理。

1、背景和目標

1.1 背景

MySQL在互聯網行業應用廣泛,性能強、可靠性高,云廠商還提供了許多擴展工具,生態相對其他數據庫而言比較成熟。

歸因于成熟的基建,業務研發人員更需關心的是數據庫設計方案、操作數據時的性能和一致性問題。如果我們在使用事務時,不知道數據存儲方式和事務實現原理,往往會在一個事務的多次讀寫過程中產生bug,即數據的變更不符合預期。因此當了解了MySQL事務的底層實現原理,我們就能知道如何編寫代碼以達到預期,就能知道數據庫引擎設計的精妙之處。

1.2 目標

詳細介紹MySQL InnoDB的數據模型、數據持久化策略、事務提交以及故障恢復原理。

2、InnoDB存儲結構

2.1 InnoDB邏輯存儲結構

InnoDB邏輯存儲結構層級:表空間->段->區->頁->行

圖片

如上圖所示,數據表有許多數據行,分別存儲在16KB的Page上,把一定數量的Page整合為了一個Extent(默認是64個Page即共1M),而多個Extent又構成了一個Segment,不同類型的Segment又組成了對應類型的表空間。

2.2 InnoDB物理存儲結構

InnoDB總體結構分為內存結構(下圖左側)和磁盤結構(右側)兩部分。

圖片

3、InnoDB磁盤結構詳解

3.1 表空間

磁盤部分包括各種表空間,包括系統表空間(System Tablespace)、獨立表空間(File-Per-Table Tablespaces)、undo表空間(Undo Tablespaces)、通用表空間(General Tablespaces)、臨時表空間(Temporary TableSpaces)5種表空間。

表空間可以看做是InnoDB存儲引擎邏輯結構的最高層 ,所有的數據都是存放在表空間中。InnoDB通過參數InnoDB_file_per_table(DMS是ON)可以選擇使用系統表空間還是獨立表空間存儲表,如果不是ON,則所有InnoDB表都保存在ibdata1這個表文件中,否則一個表占據一個表文件,擁有自己獨立的表文件(用戶記錄、索引和插入緩沖Bitmap),即每個Table單獨存儲為一個“.ibd”文件,但change buffer等依然存放在系統表空間。

3.2 段

多個段組成一個表空間。常見的段有數據段、索引段、回滾段等,段是一個邏輯的概念,是一些零散頁面和一些完整的區的集合。不同類型的數據保存在單獨的段內,可以更好的保持該類型數據的連續性,可以提升訪問磁盤的效率。創建一個索引會創建數據段和索引段,即一個索引占用兩個段。

  • 數據段:B+樹的葉子節點(Leaf node segment)
  • 索引段:B+樹的非葉子節點(Non-leaf node segment)
  • 回滾段(rollback segment):InnoDB中undo log是采用分段(segment)的方式進行存儲的,每一個rollback segment內部由1024個undo segment組成,每個undo Tablespace最多會包含128個rollback segment。每一時刻一個undo segment都是被一個事務獨占的,每個寫事務都會持有至少一個undo segment,當有大量寫事務并發運行時,就需要存在多個undo segment。MySQL 8.0由于支持了最多128個獨立的Undo Tablespace,一方面避免了ibdata1的膨脹,方便undo空間回收,另一方面也大大增加了最大的rollback segment的個數,增加了可支持的最大并發寫事務數(128*128*1024)。

注意,雖然InnoDB區分了數據段和索引段,但由于數據是以主鍵為索引來組織數據的存儲的,所以索引文件和數據文件都在同一個文件中,都在“.ibd”文件里面。

3.3 區

表空間中的頁實在是太多了,為了更好的管理這些頁面,InnoDB提出了區的概念。一個表空間劃分為多個區(extent),一個區內包含物理上連續的64個頁,因此一個區空間大小為64*16KB=1M。區就是為了保證頁的連續性,InnoDB一次會從磁盤申請4~5個區。

段可以簡單理解為是一個邏輯的概念,而Extent是一個物理概念,每次B+樹的擴容都是以Extent為單位來擴容的,默認一次擴容不超過4個Extent。

段區分了數據段和索引段,其實也就有了各自的區,即葉子節點和非葉子節點都有自己獨立的區。想象一下,當B+樹按順序范圍查詢時,如果數據分布在磁盤的不同位置,就會產生隨機IO,而如果數據的物理位置相鄰,就可以通過順序IO讀取了。

3.4 頁

頁是InnoDB中管理數據的最小單元,是固定大小的一段連續磁盤空間,默認為16KB,用于存放數據、索引等各種類型的數據。

InnoDB中,常見的頁類型有數據索引頁、undo page、文件管理頁FSP_HDR/XDES、插入緩沖IBUF_BITMAP頁、INODE頁等。

在InnoDB中的設計中,頁與頁之間是通過一個雙向鏈表連接起來,而存儲在頁中的數據行則是通過單鏈表連接起來的,如下圖:

圖片

頁有通用的文件頭和尾(將頁的內容進行封裝,通過文件頭和文件尾的checksum方式來確保頁的完整性),但是中部的內容根據頁的類型不同而發生變化。我們主要關注數據頁和索引頁,這種類型的頁包括七個部分:

  • File Header:文件頭,共38B,記錄了頁的地址、頁號、上一頁和下一頁指針、頁的類型信息、頁的校驗和checksum(校驗和在寫入磁盤前計算得到,當從磁盤中讀取時,重新計算校驗和并與數據頁中存儲的對比,如果發現不同,則會導致MySQL crash)、日志序列位置(LSN,Log Sequence Number,表示日志文件的長度,一個不斷遞增的unsigned long類型整數)等。
  • Page Header:數據頁頭,用來記錄數據頁的狀態信息,包括Free Space的地址、本頁中的記錄的數量、標記為刪除的記錄等,共56B。
  • System records:Infimum + Supremum Records。InnoDB每頁中有兩個虛擬的行記錄,用來限定記錄的邊界。Infimum記錄是比該頁中任何主鍵值都要小的記錄,Supremum記錄是比該頁中任何主鍵值都要大的記錄。這兩個記錄在頁創建時被建立,并且在任何情況下不會被刪除,并且由于這兩條記錄不是我們自己定義的記錄,所以它們并不存放在頁的User Records部分。所以如果數據是順序存儲的,那么查詢數據是否在某一頁中就無需遍歷頁中的所有數據,只需判斷這兩個記錄就行了。
  • User Records:用戶記錄,以單鏈表的形式存儲,如下圖:

圖片

  • Free Space:空閑空間,用于存放新記錄。在一開始生成頁的時候,并沒有User Records這個部分,每當插入一條記錄,就會從Free Space部分中申請一個記錄大小的空間到User Records部分,當Free Space用完時,這個頁也就使用完了。
  • Page Directory:數據目錄(彌補單向鏈表查詢性能差的缺點),InnoDB會把頁中的記錄劃分為若干個組,每個組的最后一個記錄的地址偏移量作為一個槽,存放在Page Directory中,便于二分查找定位數據。對于分組中的記錄數是有規定的:Infimum記錄所在的分組只能有 1 條記錄,Supremum記錄所在的分組中的記錄條數只能在1~8條之間,中間的其它分組中記錄數只能在是4~8條之間。所以如果數據是順序存儲的,那么查詢數據在某一頁的位置就無需遍歷頁中的所有數據,只通過二分法就可以快速定位到對應的槽,然后再遍歷該槽對應分組中的記錄就能知道了。
  • File Trailer:文件尾,共8B,包括頁的校驗和checksum(依賴于引擎選用的校驗算法,不一定與文件頭的checksum相同)、日志序列位置(LSN),與File Header中的相同。默認情況下,InnoDB每次從磁盤讀取一個頁就會檢測該頁的完整性,即File Trailer中的內容需和File Header保持一致。

3.5 行

數據行即一行一行的數據。MySQL中單行數據最大能存儲64KB=65535B,故表中字段長度加起來如果超過該值就會拒絕創建表。以utf8mb4字符集下varchar(M)為例,該字符集下一個字符最多需要4B表示,如果M大于16383,那么總字節數就會超過4*16383=65532B,所以M的最大值就是16383個字符。

雖然單行數據最大值遠大于單頁(16KB),但MySQL為了在單頁中至少存儲2行數據(每行8KB),引入了行溢出機制,即只要一行記錄的總和超過8KB,就會溢出,比如varchar(9000) 或者 varchar(3000) + varchar(3000) + varchar(3000),當實際長度大于8k的時候,會對最大字段使用uncompress BLOB page單獨存儲(即一個字段獨享一個或多個頁),而在Barracuda文件格式下字段本身只會用20B存儲溢出行的地址和占用的字節數。

InnoDB的文件格式包括舊格式Antelope和新格式Barracuda(DMS使用該格式),兩者主要的不同在于對存儲數據時所占用的空間差異,每種文件格式有自己支持的行格式,行格式就是指數據行的存儲方式,包括是否緊湊存儲(占用磁盤空間)、是否可變長度存儲、大索引前綴支持、壓縮支持。差異如下:

行格式

緊湊的存儲特性

增強的可變長度列存儲

大索引鍵前綴支持

壓縮支持

支持的表空間類型

所需文件格式

REDUNDANT(冗余)

system, file-per-table, general

Antelope or Barracuda

COMPACT(緊湊)

system, file-per-table, general

Antelope or Barracuda

DYNAMIC(動態)

system, file-per-table, general

Barracuda

COMPRESSED(壓縮)

file-per-table, general

Barracuda

通過下列指令可以查詢到數據庫的文件格式和行格式配置:

show variables like "InnoDB_file_format";
show variables like "InnoDB_default_row_format";

REDUNDANT和其他幾種類型的區別在就是在于首部的內容區別。REDUNDANT的存儲格式為首部是一個字段長度偏移列表(每個字段占用的字節長度及其相應的位移),其他類型的存儲格式為首部是一個非NULL的變長字段長度列表,這種方式存儲數據會更加緊湊(頁中存放的行數越多,性能就越高),數據布局如下圖:

  • 針對VARCHAR、TEXT、BLOB這類變長字段,列中實際存儲了多少數據是不固定的,因此除了要把數據本身存下來,還需要記下它的長度。
  • 如果字段值為NULL,其并不占該部分任何空間,除了占有NULL標志位,故兩個字段為NULL就占用2bit。
  • 頭信息中包括刪除標記、當前記錄是否是分組中的最后一條、當前記錄在頁中的相對位置、記錄類型(0:普通記錄,1:B+樹非葉子節點目錄項記錄,2:Infimum記錄,3:Supremum記錄)、下一條記錄的相對位置等。
  • 每行數據除了用戶定義的列外,還有3個隱藏列,包括trx_id列和roll_pointer列(見下文),分別為6字節和7字節的大小,若表沒有定義主鍵,每行還會增加一個6字節的rowid列。

注意,索引也是按這種方式存儲的:

  • 對于聚簇索引,非葉子節點包含主鍵和child page number,葉子節點包含主鍵和具體的行;
  • 對于非聚簇索引,也就是二級索引,非葉子節點包含二級索引和child page number,葉子節點包含二級索引和主鍵值。

4、InnoDB內存結構詳解

4.1 buffer pool

buffer pool是InnoDB的緩存,用來存放各種數據,包括索引頁(index page)、數據頁(data page)、undo頁、插入緩沖、自適應哈希索引(AHI)、innodb存儲的鎖信息、數據字典等。把磁盤上的數據加載到緩沖池中(通過預讀機制加載當前頁、相鄰頁),可避免每次訪問都進行磁盤IO,起到加速訪問的作用。應用程序在對數據庫執行增刪改操作的時候,實際上主要都是針對內存里的buffer pool中的數據進行的。

buffer pool包含三種數據類型:

  • free page:從未用過的頁。
  • clean page:干凈的頁,即數據頁的數據和磁盤一致。
  • dirty page:臟頁,即數據頁的數據和磁盤不一致。

針對這3種頁,InnoDB使用3種鏈表維護:

  • free list:空閑頁鏈表,管理free page。
  • flush list:臟頁鏈表,管理dirty page并在某個時刻對該鏈表的臟頁進行刷盤,按臟頁的修改時間排序,更新操作較早的臟頁先被刷盤。
  • lru list:正在使用的內存頁鏈表,里面包含clean page和dirty page,也就是說lru list中的頁包含flush list中的所有臟頁。lru list遵循lru算法管理緩存頁。

InnoDB需要保證buffer pool的數據都是熱點數據,將無效的預讀數據快速刪除、不將讀入后立即使用的數據替換熱點數據,就引入了變種lru算法(新生代+老生代、老生代停留時間窗口)來解決“預讀失效”與“緩沖池污染”的問題。通過下列指令可以查詢到數據庫設置冷熱分界線和成為熱塊的所需時間:

show variables like 'InnoDB_old_blocks_pct'; -- 單位%,默認37,代表冷數據占比
show variables like 'InnoDB_old_blocks_time'; -- 單位ms,默認1000

Mysql5.7.5之后,buffer pool有分塊(chunk)的特性,即一個buffer pool實例是由多個塊組成,每個塊的塊內空間是連續的,塊與塊之間則是離散的。分塊是為了方便用戶在mysql運行期間能夠調整buffer pool的大小。

注意,為了提高讀寫性能,避免過少的數據刷盤或隨機IO,buffer pool一般不會對單個Page實時刷盤,所以這就出現了緩存和磁盤的一致性問題,InnoDB通過引入redolog來保存數量操作記錄從而解決此問題,見下文。

4.2 change buffer

change buffer(寫緩存)是一種特殊的數據結構,可以避免數據更改時因為隱式查詢數據帶來的磁盤IO。change buffer默認占buffer pool的 25%,最大允許占50%。可以根據寫業務的量調整,寫操作越頻繁,change buffer帶來的性能提升越明顯。

change buffer工作原理如下:

  1. 當更改的頁存在于buffer pool的lru list,則直接在緩沖池中修改這個頁,這個頁會變成臟頁,鏈入到flush list中,但并不馬上刷盤;此時不涉及change buffer操作。
  2. 當更改的頁不存在于buffer pool的lru list,就要先從磁盤讀取要修改的數據頁到buffer pool后再修改(數據不會在磁盤中直接更改)。但為了避免修改操作引發的磁盤讀IO,系統會將DML操作記錄到change buffer中,并不馬上刷盤。等下次對這些修改的頁進行查詢時,由于lru list不存在該頁,會從磁盤讀取(磁盤頁是更改前的數據),為了避免讀到臟數據,該磁盤頁會和change buffer中的更改合并后才鏈入到lru list。如果未來一段時間都不會查詢到這個修改了的頁,也會有insert buffer thread定時將change buffer的數據合并到磁盤頁中。
  3. 如果做出的更改是對唯一鍵索引的值的修改,InnoDB要做唯一性校驗,必須查詢磁盤,再在lru list上的頁修改,不會在change buffer中操作。

綜上:change buffer適合寫多讀少的場景,并且滿足非唯一索引。

4.3 Adaptive Hash Index

Adaptive Hash Index(AHI,自適應哈希索引),是指InnoDB存儲引擎通過監控表上索引頁的查找模式,自動根據查找模式對“熱點數據”來創建哈希索引。因為對B+樹索引的訪問需要依次訪問根節點>中間節點>葉子節點,而對哈希索引的訪問僅需要一次HASH計算即可定位到目標位置。一些資料統計,啟用AHI后,讀取和寫入速度可以提高2倍,輔助索引的連接操作性能可以提高5倍。

通過下列指令可以查詢到數據庫的相關設置:

show variables like '%hash_index';(DMS設置的是OFF)

AHI使用條件:

  1. 索引被訪問了17次(BTR_SEARCH_HASH_ANALYSIS)
  2. 索引中的某個頁已經被訪問了至少100次(BTR_SEARCH_BUILD_LIMIT)
  3. 數據頁被相同模式(相同的查詢條件)訪問N次(N=頁中記錄*1/16)

AHI使用buffer pool中的數據頁進行構造,僅保存在內存中,且僅對熱點數據進行處理,因此構造AHI速度極快。

4.4 log buffer

log buffer就是redolog buffer的簡稱,是存儲要寫入磁盤上的redolog的內存區域。

log buffer由變量innodb_log_buffer_size定義大小,默認為16MB(DMS中設置了8GB)。log buffer的內容會根據設置刷盤,足夠大的log buffer可以使得大事務完全依賴緩存運行,而不需要在事務提交前將redolog數據寫入磁盤。因此,如果有更新、插入或刪除許多行的事務,增加log buffer的大小可以節省磁盤I/O。

log buffer是順序寫的,刷盤也是順序的,所以當某個臟頁對應的redolog從log buffer刷盤時,會保證將在其之前產生的redolog也刷盤,詳情見下文redolog的介紹。

5、三種log類型和作用

5.1 undolog

undolog是InnoDB的日志,又稱撤銷日志文件,屬于邏輯日志。undolog內存數據存儲在buffer pool中,磁盤數據則存儲在undo tablespace。

undolog保存類型為FIL_PAGE_UNDO_LOG在undo page中,一個undo page可以保存多條undolog記錄。每條undolog記錄包含該undolog在undo page的頁內地址、undolog對應的記錄所在的tableId(tableId全局唯一)、undolog類型、undolog編號、下一條undolog的地址、old_trx_id、old_roll_pointer、主鍵的每個列占用的存儲空間大小和真實值、被修改字段的修改前后信息等。

undolog提供回滾和多個行版本控制(MVCC)的兩個能力,保證了事務的原子性:

  • 回滾:undolog分為3類,包括TRX_UNDO_INSERT_REC、TRX_UNDO_DEL_MARK_REC、TRX_UNDO_UPD_EXIST_REC,分別對應增、刪、改操作。上文講到每一行數據有兩個隱藏字段trx_id和roll_pointer,它們主要作用于數據庫的事務。所謂的事務就是指對一個或多個數據庫的一系列操作,這些操作需保證ACID的規則。當某個事務執行過程中對某個表執行了增、刪、改操作,InnoDB會給該事務分配一個遞增的獨一無二的trx_id(全局變量,每增加256時會刷盤),而且會生成對應的undolog,而roll_pointer就是一個指向記錄對應的undolog的一個指針,數據行會存儲最近提交的trx_id和roll_pointer。事務開啟后,在本事務或其他事務(根據事務隔離級別)對該數據行的一次次修改中,生成的undolog會記錄old_trx_id(修改該記錄的上一次的trx_id)和old_roll_pointer(對應undolog地址),這樣就生成了一個版本鏈,當需要回滾時就能沿著old_trx_id和old_roll_pointer找到一條記錄的所有歷史版本,從而實現事務的回滾能力。
  • MVCC:InnoDB復用了undolog中已經記錄的歷史版本數據來實現MVCC機制。當用戶讀取一行記錄時,若該記錄已經被其他事務占用,當前事務可以通過undolog讀取之前的行版本信息,以此實現非鎖定讀取。此外,根據trx_id是遞增的特性,InnoDB還引入了ReadView機制,用于保存創建事務時的活躍trx_id。ReadView有三個屬性,分別是m_ids(活躍的trx_id列表)、min_trx_id(活躍的最小trx_id)、max_trx_id(下一個該分配的trx_id),基于這三個屬性,實現了READ COMMITTED和REPEATABLE READ兩種隔離級別。

因為一個事務可能包含多個增、刪、改操作,為了提高并發執行多個事務寫入undolog的性能,InnoDB將各個事務的各種操作通過上文提到的undo segment分開存儲(undo segment的undo page通過鏈式存儲,即每個事務都有自己的insert undo鏈表、update undo鏈表),而每個段的第一個undo page通過TRX_UNDO_STATE屬性存儲了該段的一些事務信息,取值有下面幾個:

  • TRX_UNDO_ACTIVE: 活躍狀態,即一個活躍的事務正在往這個段里邊寫入undolog。
  • TRX_UNDO_CACHED:被緩存的狀態,即該狀態下的段等待著之后被其他事務重用。
  • TRX_UNDO_TO_FREE: 可以釋放,對于insert undo鏈表來說,如果在它對應的事務提交之后,該鏈表不能被重用,那么就會處于這種狀態。
  • TRX_UNDO_TO_PURGE: 可以清理,對于update undo鏈表來說,如果在它對應的事務提交之后,該鏈表不能被重用,那么就會處于這種狀態。
  • TRX_UNDO_PREPARED: 準備狀態,還未提交。

在事務未提交前TRX_UNDO_STATE是TRX_UNDO_PREPARED狀態,事務提交后,根據不同的操作類型轉換成TRX_UNDO_CACHED、TRX_UNDO_TO_FREE或者TRX_UNDO_TO_PURGE狀態,表示滿足一定條件后可以清理這些undolog,事務如果需要回滾的話,必須是TRX_UNDO_ACTIVE或者TRX_UNDO_PREPARED狀態,故事務的提交是由該屬性判斷的,詳情見下文的事務執行流程。

5.2 redolog

redolog是InnoDB存儲引擎層的日志,又稱重做日志文件,屬于物理日志。redolog內存數據存儲在log buffer中,磁盤數據則存儲在以ib_logfile0、ib_logfile1…命名的日志文件中。

上文提到,InnoDB通過buffer pool(包括change buffer、undolog)提高讀寫性能,但如果進程或機器崩潰會導致緩存丟失,為了能實現故障恢復就引入了redolog。事務在執行過程中對數據庫所做的所有修改(聚集索引、二級索引、undolog等修改)都會生成對應的redolog,并保證redolog早于緩存落盤(WAL機制),當故障發生后,InnoDB會在重啟時,通過重放redolog來恢復所做的修改。

到MySQL8.0為止,為了應對各種各樣不同的需求,InnoDB已經有多達65種(上限127種)的redolog類型用來記錄各種信息,而恢復數據時需要判斷不同的類型,來做對應的解析。redolog長度是動態的,常見的數據結構包括日志類型、Space ID、頁號、數據頁中的偏移量、修改的長度和具體的值。

圖片

根據redolog不同的作用對象,可以將這些類型劃分為三個大類:作用于Page、作用于Space以及提供額外信息的Logic類型。redolog記錄的是作用于頁的,如果作用于Space,那么頁號的值為0。

不管是在內存還是磁盤中,redolog都以塊為單位進行存儲,默認每個塊占512B,等于磁盤扇區的大小,這稱為redolog block。每個redolog block由3部分組成:日志塊頭(12B)、日志塊尾(4B)和日志主體(492B),log buffer則是由若干個連續的redolog block組成的,總數不能超過1GB個(基于LSN的長度限制)。

InnoDB為了提高redolog的性能和保證數據一致性,還引入的mini-transaction機制(簡稱mtr),mtr就是redolog組的概念,比如對一些頁面的訪問、向聚簇索引或二級索引插入一條記錄等操作時產生的redolog是不可分割的(插入數據如果引起索引分裂,會產生許多redolog)。每組的最后一條redolog后邊會加上一條類型為MLOG_MULTI_REC_END的redolog,來標識該組的結束。

log buffer中寫入redolog的過程是順序的,但不是一條一條寫入,而是一個mtr完成后,將里面所有的redolog一起復制到log buffer中(還會把執行過程中可能修改過的頁面加入到Buffer Pool的flush鏈表),也就是存儲到redolog block中,可能占用不到一個block,也可能占用多個block。一個事務可以包含多個mtr,那么多個事務的mtr就會有交集,事務間的mtr會相互穿插。

5.3 binlog

binlog是屬于MySQL Server層面的,又稱為歸檔日志,屬于邏輯日志,是以二進制的形式記錄的,是sql語句的原始邏輯,主要是用于進行集群中保證主從一致以及執行異常操作后恢復數據。

binlog日志文件默認大小由磁盤決定,順序追加寫入。binlog內存數據存儲在binlog cache中(大小由binlog_cache_size控制),磁盤數據則存儲在binlog file中。

binlog有三種格式,分別是Row、Statement、Mixed。

  • Row格式記錄了操作語句對具體行的操作以及操作前的整行信息,缺點是占空間大(一條sql影響的行數),優點是能保證數據安全,不會發生遺漏,是5.7版本默認格式。
  • Statement格式記錄了修改的sql(只是一條sql語句),缺點是在集群中可能會導致操作不一致從而使得數據不一致,如執行now()函數可能會導致不同機器值不同。
  • Mixed格式會針對于操作的sql選擇使用Row還是Statement,相比于Row更省空間,但還是可能發生主從不一致的情況。

binlog和redolog雖然都保存了記錄的修改日志,但兩者有一些區別:

  • binlog是邏輯日志,記錄的是對哪一個表的哪一行做了什么修改;redolog是物理日志,記錄的是對哪個數據頁中的哪個記錄做了什么修改。
  • binlog是追加寫;redolog是循環寫,日志文件有固定大小,會覆蓋之前的數據。
  • binlog是Server層的日志;redolog是InnoDB的日志。如果不使用InnoDB引擎,就沒有redolog。

6、InnoDB持久化策略

6.1 InnoDB兩種持久化策略

InnoDB內存部分包括緩沖池(buffer pool) 和日志緩沖(log buffer),兩者刷盤方式不同,前者走direct_io模式(直接繞過Page Cache來訪問磁盤),后者走Page Cache模式(IO操作需要委托操作系統來完成)。

  1. 是否使用Page Cache的區別是什么?

OS的Page Cache對讀寫做了不少優化,包括按順序預讀取(按頁讀取)、在成簇磁盤塊(n次方個扇區)上執行IO、允許訪問同一文件的多個進程共享高速緩存的緩沖區等,但數據必須在用戶進程與內核互相拷貝。

direct_io的優點是減少操作系統緩沖區和用戶地址空間的拷貝次數,降低了CPU和內存帶寬的開銷。而InnoDB本身也已處理好buffer pool與磁盤數據的對應關系,所以可以舍去Page Cache。

  1. 先刷buffer pool還是先刷log buffer?

先寫日志,再寫磁盤(WAL機制,Write-Ahead Logging),即redolog和binlog等日志數據刷盤到log文件完成后,才會將臟頁從buffer pool刷盤到表文件。

為什么運用WAL機制?因為順序寫磁盤的性能堪比寫內存,所以寫日志會比數據刷盤的性能高很多,只要保證日志寫入成功,再通過代碼保證日志和需刷盤數據的一致性,就能在保證數據不丟失的情況下大大提高性能。

順序寫運用很廣泛,比如kafka追加寫實現了事務消息,即提交或回滾事務時,會追加寫入一條控制類型的消息來標識是commit或rollback。

6.2 buffer pool持久化過程

buffer pool刷盤時機主要有以下四種:

  • MySQL正常關閉之前,會把所有的臟頁刷盤;
  • Master Thread會以每秒或者每10秒一次的頻率定期將適量的臟頁刷盤。上文講到buffer pool通過變種lru算法區分冷熱數據,故后臺線程會優先刷冷數據,因為熱數據在短時間可能被多次修改,如果優先刷盤熱數據頁,這個頁很快又會被修改,又需要再刷盤,不如等它變成冷數據再刷盤。
  • lru空閑列表不足、log buffer或磁盤空間不足時,page cleaner線程會異步將臟頁刷盤。
  • buffer pool空間不足時,用戶線程從磁盤讀取某個頁要鏈入lru list,lru list會釋放尾部的一個頁。假設這個釋放的頁是一個臟頁,那么用戶線程就不得不親自把這個臟頁刷盤,這樣就會降低響應用戶請求的速度。之所以需要后臺線程定時刷盤臟頁就是為了盡可能避免發生這種主動刷盤的情況。

InnoDB還引入了double write buffer物理存儲空間,來處理buffer pool刷盤時的異常情況。buffer pool的臟頁要刷盤時,數據頁的空間為16KB,OS文件系統的頁空間一般為4KB,磁盤的扇區每片一般為512B,最終都會一片片的刷扇區。計算機硬件和操作系統,在極端情況下(比如斷電)往往并不能保證這一操作的原子性,如果16KB的數據在寫入4KB時發生了系統斷電/os crash,只有一部分寫是成功的,這種情況寫就是partial page write

mysql在恢復的過程中是檢查頁的checksum(頁的校驗和,見上文),發生partial page write問題時, Page已經損壞,找不到該頁的checksum,就無法通過redolog恢復。

因此根據上述問題,InnoDB將buffer pool中的臟頁刷盤時,會先通過memcpy函數將Page刷到double write buffer,再將數據拷貝到數據文件對應的位置。

double write buffer是物理磁盤上共享表空間中連續的128個頁(每頁16KB,大小共2MB, 每次寫入1MB)。

  • 如果寫double write buffer失敗,那么這些數據不會刷盤,InnoDB會載入磁盤原始數據和redo日志比較,并重新刷到double write buffer,然后再刷盤。
  • 如果寫double write buffer成功,但是刷盤失敗(partial page write問題),那么InnoDB就不會通過事務日志來恢復了,而是直接用double write buffer中的數據刷盤。

6.3 redolog持久化過程

redolog包括兩部分:一是內存中的日志緩沖(log buffer),該部分日志是易失性的;二是磁盤上的重做日志文件(redolog file,以ib_logfile0、ib_logfile1…命名),該部分日志是持久的。

redolog可以通過參數InnoDB_log_files_in_group配置成多個文件(最大100),另外一個參數InnoDB_log_file_size表示每個文件的大小,因此總的redolog大小為InnoDB_log_files_in_group * InnoDB_log_file_size。

上文講到內存中log buffer是由多個redolog block組成的(日志塊頭占12B、日志塊尾占4B),那么redolog file也是如此,每個redolog file的前4個block用于表示文件頭,存儲了一些管理信息,往后則存儲log buffer中的block鏡像。文件頭主要存儲了標記redolog file開始的LSN值(Log Sequence Number的簡稱)、標記redolog已刷盤的全局變量flushed_to_disk_lsn值、標記臟頁已刷盤的全局變量checkpoint_lsn等。

  • LSN:LSN記錄了已經寫入的redolog的日志量,是一個全局變量,初始值為8704。每次寫入一個mtr時,LSN就會累加上mtr所占的空間字節數和相應的block頭尾空間字節數。比如mtr_1產生的redolog為200B,那么LSN就變成了8704+12+200=8916,之后mtr_2又產生了1000B的redolog,那么LSN就變成了8916+296+4+512+12+208=9948。
  • flushed_to_disk_lsn:系統第一次啟動時,flushed_to_disk_lsn值和初始的LSN值是相同的,都是8704。隨著系統的運行,redolog被不斷寫入log buffer,但是并不會立即刷盤,LSN的值就和flushed_to_disk_lsn的值拉開了差距,如果兩者的值相同時,說明log buffer中的所有redolog都已經刷盤了。
  • checkpoint_lsn:checkpoint_lsn的初始值也是8704,當flush鏈表中的臟頁按順序被刷盤時,mtr生成的對應redolog就可以被覆蓋了,所以我們可以進行一個增加checkpoint_lsn的操作,我們把這個過程稱之為做一次checkpoint。臟頁是與redolog有關聯的,記錄了redolog的LSN信息,通過臟頁可以找到對應的redolog,通過redolog也可以恢復對應的臟頁。

下圖展示了一組4個文件的redolog日志,checkpoint_lsn之前的空間表示可以進行寫的文件。

圖片

我們再看下log buffer刷盤的具體過程:

  1. 客戶端向數據庫發送寫命令。
  2. 數據庫收到寫命令。
  3. 數據庫通過系統調用將數據寫入內核緩沖區(Page Cache)。
  4. 操作系統將緩沖區數據傳輸至磁盤控制器,暫存在磁盤緩沖區。
  5. 磁盤控制器將數據精準的寫入物理磁盤。

如果數據庫停機,那么第三步之后操作系統可以保證數據寫入磁盤;如果是操作系統停機,此時磁盤也無法正常工作,那就必須完成這五步才能保證數據落盤。

如上所述,在將寫操作寫入redolog的過程中也不是直接就進行磁盤IO來完成的,而是分為三個步驟:

圖片

  1. 寫入log buffer中,這部分是屬于MySQL的內存中,是全局公用的。
  2. 在事務編寫完成后,就可以執行write操作,寫到文件系統的Page Cache中。
  3. 執行fsync(持久化)操作,將Page Cache中的數據正式寫入磁盤上的redolog文件中,也就是圖中的hard disk。

InnoDB_flush_log_at_trx_commit參數控制了log buffer的刷盤時機(值可為0、1、2,默認1):

  • 設置為0:每隔1秒從log buffer寫入Page cache,并馬上刷盤,mysql服務故障或者主機宕機則丟失1秒(由log buffer的innodb_flush_log_at_timeout參數控制)數據。
  • 設置為1:事務提交時,立刻從log buffer寫入Page cache, 并馬上刷盤,mysql服務故障或者主機宕機不會丟失數據,但會頻繁發生磁盤IO。
  • 設置為2:事務提交時,立刻從log buffer寫入Page cache,每隔1秒刷盤,mysql服務故障不會丟失數據,因為數據已經進入操作系統緩存,與mysql進程無關了,主機宕機則丟失1秒數據。

除此之外,當log buffer空間不足、做checkpoint、Mysql正常關閉、binlog切換等情況也會觸發redolog刷盤。刷盤操作是異步IO,由專門的線程完成這件事,不會阻塞用戶請求的處理。redolog如果沒有及時刷盤或者只刷盤一部分,是會導致事務丟失的。

6.4 undolog持久化過程

InnoDB的undolog嚴格的講不是Log,而是數據,因此他的管理和落盤都跟數據一樣:

  • undolog的磁盤結構并不是順序的,而是像數據一樣按Page管理。
  • undolog寫入時,也像數據一樣產生對應的redolog。
  • undolog的Page也像數據一樣緩存在Buffer Pool中,跟數據Page一起做lru換入換出,以及刷臟。undo page的刷臟也像數據一樣要等到對應的redolog落盤之后。

之所以這樣實現,首要的原因是undolog需要承擔MVCC對歷史版本的管理作用,設計目標是高事務并發,方便的管理和維護,因此當做數據更合適。

6.5 binlog持久化過程

binlog也有獨立的刷盤策略,通過sync_binlog參數控制(值分別為0、1、N,默認為1):

  • 設置為0 :每次提交事務都只將binlog cache進行write,不fsync。
  • 設置為1 :每次提交事務都會將binlog cache進行write,并執行fsync。
  • 設置為N :表示每次提交事務都會將binlog cache進行write,但累積N個事務后才fsync。

由于binlog是屬于MySQL Server層面的日志,只需追加寫入即可。

7、MySQL事務提交和崩潰恢復

7.1 MySQL中的XA協議

有一個名叫X/Open的組織提出了一個名為XA的規范。這個XA規范提出了2個角色:

  • 一個全局事務由多個小的事務組成,所以我們得在某個地方找一個總攬全局的角色用于和各個小事務進行溝通,指導它們是提交還是回滾。這個角色被稱作事務協調器(Transaction Coordinator)。
  • 管理一個小事務的角色被稱作事務管理器(Transaction Manager)。

要提交一個全局事務,那么屬于該全局事務的若干個小事務就應該全部提交,只要有任何一個小事務無法提交,那么整個全局事務就應該全部回滾。XA規范中指出,要提交一個全局事務,必須分為2步:

  • Prepare階段:當協調器準備提交一個全局事務時,會依次通知各個管理器把在事務執行過程中所產生的數據都刷盤。
  • Commit階段:如果在Prepare階段各個管理器都完成了數據的刷盤,那么協調器就要真正通知各個管理器去提交事務了,否則就需要讓這些管理器回滾事務了。

XA規范把上述全局事務提交時所經歷的兩個階段稱作兩階段提交。在單個MySQL實例中,將server層作為事務協調器,存儲引擎作為事務管理器,故本文將binlog作為事務協調器。

7.2 sql執行流程

sql提交到MySQL時需要進行詞法語法分析、優化(如果沒有命中索引,就會掃全表),才會執行:

圖片

7.3 事務執行流程

假設我們要更新一條數據,語句如下:

update T set c=c+1 where ID=2;

  1. Server層的執行器先調用引擎取出ID=2這一行。ID是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2這一行所在的數據頁本來就在內存中,就直接返回給執行器;否則需要先從磁盤讀入內存,然后再返回。
  2. 執行器拿到數據把這個值+1(分配trx_id,開始記錄事務),得到新的數據,再調用存儲引擎接口寫入這行新數據。此處會先記錄undolog,并將undolog對應的變化信息redolog保存到log buffer中,然后再去修改buffer pool,并且把buffer pool對應的變化信息redolog記錄到log buffer中,詳情見上文。
  3. InnoDB做完上述操作后,就準備提交事務了。此時處在Prepare階段,執行器調用binlog_prepare接口,就會將上文提到的undo segment的狀態置為TRX_UNDO_PREPARED,并將本次提交事務的XID也寫入其中,同時生成對應的redolog。此時根據redolog的刷盤策略,本次事務對應的log buffer可能會被刷盤,而只要log buffer刷盤成功,那么即使之后系統崩潰,在重啟恢復的時候也可以將處于Prepare狀態的事務完全恢復(恢復buffer pool和undolog),然后回滾或者再次提交事務。
  4. 而到Commit階段,執行器繼續調用binlog_commit接口提交事務,此時會先將事務執行過程中產生的binlog(包括XID)按照binlog的刷盤策略刷入磁盤,再根據不同的操作類型把undo segment的狀態轉換成TRX_UNDO_CACHED、TRX_UNDO_TO_FREE或者TRX_UNDO_TO_PURGE(這幾個狀態是InnoDB的事務結束的標志),表示滿足一定條件后可以清理這些undolog,并將對應的redolog刷盤。至此這個事務就算是提交完了,注意事務提交需要三次刷盤(寫redolog,寫binlog,寫commit,InnoDB新版本通過組提交進行了優化)。而臟頁并不一定隨著事務提交而刷盤,需依賴于buffer pool持久化策略。
  5. 對于處于Prepare狀態的事務,存儲引擎既可以提交,也可以回滾,這取決于目前該事務對應的binlog是否已經寫入硬盤。這時就會讀取最后一個binlog日志文件,從日志文件中找一下有沒有該Prepare事務對應的XID記錄,如果有的話,就將該事務提交,否則就回滾好了。

7.4 如果沒有兩階段提交

redolog未寫入,binlog未寫入:此時MySQL異常重啟無法恢復數據,認為sql就沒執行。

redolog寫入,binlog未寫入:此時MySQL異常重啟能根據redolog恢復事務提交時的數據,但binlog沒有記錄,后續使用binlog恢復臨時庫會出現數據丟失,導致狀態不一致。

binlog寫入,redolog未寫入:此時MySQL異常重啟臨時庫能根據binlog重放事務提交時的數據,但redolog沒有記錄,如果主庫有一些臟頁已經刷盤,本應先回滾再通過binlog重放,但現在無法回滾,會導致狀態不一致。

7.5 結論

所謂兩階段提交,就是指同時將redolog和binlog都寫成功,這樣既能保證通過binlog恢復臨時庫時和主庫無差異,又能保證通過redolog恢復主庫時和臨時庫無差異。

責任編輯:龐桂玉 來源: 得物技術
相關推薦

2024-05-21 14:12:07

2023-07-26 09:24:03

分布式事務分布式系統

2025-06-19 08:03:03

2025-06-10 08:02:15

2025-05-16 07:46:11

分布式事務服務

2022-03-28 10:44:51

MySQL日志存儲

2024-01-26 08:18:03

2018-10-29 08:44:29

分布式兩階段提交事務

2023-11-29 07:47:58

DDIA兩階段提交

2023-12-05 09:33:08

分布式事務

2024-12-06 07:10:00

2021-01-12 14:46:34

Kubernetes開發存儲

2017-08-30 18:15:54

MySql

2024-07-22 08:57:58

2023-01-18 10:35:49

MySQL數據庫

2020-02-03 12:12:28

MySQL數據庫SQL

2021-07-23 13:34:50

MySQL存儲InnoDB

2024-03-26 16:24:46

分布式事務2PC3PC

2023-11-16 09:01:37

Hadoop數據庫

2021-10-12 19:12:15

單步實現系統
點贊
收藏

51CTO技術棧公眾號

日韩欧中文字幕| 日韩一级在线播放| 69久久精品| 国产一级成人av| 国产精品一区二区在线观看网站| 亚洲精品久久久久久久久| 在线观看成人av| 亚洲欧美一二三区| 久久久久久久久久久久久久久久久久久久| 国产精品免费aⅴ片在线观看| 2019亚洲日韩新视频| 亚洲妇女无套内射精| 网友自拍视频在线| 日韩和欧美一区二区三区| 精品久久一区二区| 黄色录像特级片| 国产一区二区三区中文字幕 | 五月婷婷色综合| 91国产丝袜在线放| 国产精品白丝喷水在线观看| 成人黄页网站视频| 国产欧美一区二区精品性色超碰| 2019日本中文字幕| 日本一卡二卡在线| 狠狠躁少妇一区二区三区| 成人午夜视频在线观看| 欧美高清电影在线看| 女王人厕视频2ⅴk| 黄页在线观看免费| 波多野洁衣一区| 韩剧1988免费观看全集| 黄色污在线观看| sis001欧美| 国产日韩精品一区二区三区| 国产大片精品免费永久看nba| 亚洲色成人网站www永久四虎| 欧洲av一区二区| 国产精品久久久久久久久动漫| 国产精品久久久久久久久| 精品无码在线观看| 国产精品亲子伦av一区二区三区| 国产精品国模大尺度视频| 精品欧美一区二区久久久伦 | 国产精品a级| 精品国产一二三| 欧美 日本 亚洲| 国产在线视频你懂得| 蜜桃av一区二区| 九九热99久久久国产盗摄| 伊人网综合视频| 中文字幕区一区二区三| 日韩欧美中文第一页| 午夜老司机精品| www.亚洲天堂.com| 久久激情网站| 不卡av电影在线观看| 性活交片大全免费看| 亚洲深夜视频| 国产精品传媒在线| 亚洲一区二区三区色| 亚洲成人精品女人久久久| 国产欧美激情| 久久人人爽亚洲精品天堂| 91超薄肉色丝袜交足高跟凉鞋| 国产麻豆一区二区三区| 黑人与娇小精品av专区| 一区二区免费电影| 女女色综合影院| 91丨porny丨中文| 国产欧美日韩中文字幕在线| 久久久久成人精品无码| 成人3d精品动漫精品一二三| 欧美大片免费久久精品三p| 少妇性饥渴无码a区免费| 成人免费高清| 久久久91精品国产一区二区三区| 亚洲iv一区二区三区| 中文字幕 国产精品| 在线看片一区| 粗暴蹂躏中文一区二区三区| 国产97免费视频| 欧美日韩老妇| 亚洲成人网av| 日本成人xxx| www.亚洲一二| 欧美一区二区精品| 岛国av在线免费| 欧美色网一区| 欧美日韩免费在线| 午夜免费高清视频| 色戒汤唯在线观看| 亚洲乱码国产乱码精品精的特点| 欧美日韩在线观看一区二区三区| 黄片毛片在线看| 国产麻豆日韩欧美久久| 精品免费日产一区一区三区免费| 国产毛片在线看| 久久综合av免费| 极品日韩久久| 在线视频二区| 五月婷婷久久丁香| 午夜免费福利在线| 视频一区在线免费看| 欧美日韩国产麻豆| 五月激情五月婷婷| 在线免费成人| 欧美福利视频一区| 午夜xxxxx| 国产一区一区| 亚洲视频自拍偷拍| 色欲狠狠躁天天躁无码中文字幕 | 国产三级小视频| 免费久久99精品国产| 91精品国产综合久久久久久丝袜 | 久久免费偷拍视频| 佐佐木明希av| 国产淫片在线观看| 亚洲人成电影网站色mp4| 亚洲一区二区三区在线观看视频| 不卡av免费观看| 欧美精品三级在线观看| 强伦人妻一区二区三区| 免费黄色成人| 在线免费观看羞羞视频一区二区| 亚洲无人区码一码二码三码的含义| 最新欧美人z0oozo0| 欧美精品videossex性护士| 久久久久亚洲av无码专区| 日韩av一区二区三区四区| 国产精品av在线播放| 日本加勒比一区| 亚洲精品久久嫩草网站秘色| 免费cad大片在线观看| 激情图片在线观看高清国产| 欧美一区二区三区性视频| 手机看片福利视频| 久久精品动漫| 蜜桃在线一区二区三区精品| 97在线观看免费观看高清| 欧美性xxxxxxx| 99久久免费看精品国产一区| 国内成人在线| 国产不卡视频在线| 神马久久久久| 国产精品国产自产拍高清av王其 | 亚洲成人av片在线观看| 日韩a级片在线观看| 激情小说亚洲一区| 国产高清在线精品一区二区三区| 深爱五月激情五月| 国产精品嫩草久久久久| 成人性生生活性生交12| 国产成人ay| 久久福利视频导航| 国产成人精品一区二三区| 日韩激情一区二区| 日韩精品无码一区二区三区| 18+激情视频在线| 欧美日韩在线影院| 一级性生活大片| 欧美在线网址| 国产高清自拍一区| 九色porny自拍视频在线播放| 亚洲成人久久电影| 成人毛片18女人毛片| 久久综合九色综合欧美98 | 狠狠色综合日日| 国产树林野战在线播放| 香蕉大人久久国产成人av| 亚洲欧洲午夜一线一品| 999视频在线| 国产精品不卡视频| 丰满少妇中文字幕| 日韩精品免费一区二区在线观看 | www.av视频在线观看| 蜜臀国产一区二区三区在线播放| 亚洲人成网站在线播放2019| 自拍偷拍亚洲| 欧美激情欧美激情| 蜜桃成人在线视频| 午夜在线电影亚洲一区| 亚洲av片不卡无码久久| 国产综合网站| 久久久久久久久久码影片| 97影院秋霞午夜在线观看| 精品免费99久久| 日韩一级片中文字幕| 亚洲欧洲日韩av| 亚洲欧洲日本精品| 欧美日韩午夜| 午夜精品区一区二区三| 影音先锋欧美激情| 国产精品v日韩精品| 免费大片黄在线观看视频网站| 日韩久久久精品| 无码人妻精品一区二| 亚洲视频图片小说| 精品无码在线视频| 久久成人久久爱| 亚洲午夜精品久久| 97视频一区| 国产精品久久久久久久久久新婚 | 99re在线视频这里只有精品| 人人妻人人澡人人爽欧美一区双 | 欧美一区二区在线视频观看| 美女视频在线免费| 久久韩剧网电视剧| 国外av在线| 精品欧美黑人一区二区三区| 一级黄色大毛片| 综合久久国产九一剧情麻豆| 91丝袜在线观看| 韩国v欧美v亚洲v日本v| 97xxxxx| 激情久久婷婷| 成人性做爰片免费视频| 精品一区二区三| 精品视频第一区| 亚洲国产中文在线| 成人妇女免费播放久久久| h片在线播放| 中文字幕欧美精品在线| 一卡二卡在线观看| 日韩欧美黄色动漫| 国产在线精品观看| www.亚洲色图.com| 无码aⅴ精品一区二区三区浪潮| 国产国产精品| 成人动漫在线观看视频| 狠狠操一区二区三区| 久久97精品久久久久久久不卡| aⅴ在线视频男人的天堂| 国产视频自拍一区| 在线中文字幕网站| 一本色道久久综合亚洲精品按摩| 日本一本高清视频| 亚洲一级二级在线| 日韩精品无码一区二区三区久久久| 成人晚上爱看视频| 精品国产免费久久久久久婷婷| 国产一区二区在线视频| 青青青青草视频| 精品精品99| 欧美一二三四五区| 免费观看久久av| 欧美性色黄大片人与善| 亚洲人成网77777色在线播放| 国产精品综合网站| 福利网站在线观看| 欧美精品成人在线| 成人女同在线观看| 97久久久久久| 中文在线免费视频| 国产成人午夜视频网址| 91精品影视| 国产精品视频专区| 亚洲高清国产拍精品26u| 国语自产精品视频在免费| av网站在线免费播放| 一区二区三区久久精品| 99中文字幕一区| 久久精品中文字幕一区| 97超碰资源站在线观看| 欧美日本中文字幕| 超碰97免费在线| 欧美中文在线字幕| 最新国产在线拍揄自揄视频| 欧美日韩福利电影| h片在线观看视频免费| www.日韩.com| 免费毛片在线| 少妇高潮 亚洲精品| 三级视频在线播放| 欧美tk—视频vk| 香蕉视频国产在线| 日韩一级片在线观看| 看黄色一级大片| 欧美日韩一区二区三区四区五区 | 日本一区二区视频在线播放| 中文字幕在线观看一区二区| 欧美三根一起进三p| 国产拍欧美日韩视频二区| 国产成人免费在线观看视频| 国产精品18久久久| 国产精品v日韩精品v在线观看| 精品一区二区在线播放| 亚洲一区和二区| 国产亚洲精品福利| 久热这里有精品| 欧美视频在线看| 97在线公开视频| 欧美日韩三级一区| 成人黄色三级视频| 日本高清免费不卡视频| 久久婷婷国产麻豆91| 色综合天天做天天爱| 国产精品无码一区二区桃花视频 | 国产精品mv在线观看| 国产福利视频在线播放| 国产一区二区三区在线观看免费视频 | 国产无人区码熟妇毛片多| 欧美电影在线免费观看| 免费成人av电影| 欧美黑人国产人伦爽爽爽| 日本在线中文字幕一区二区三区| 99国内精品久久久久久久软件| 国产欧美日韩一区二区三区四区| 奇米777四色影视在线看| 奇米色777欧美一区二区| 韩国三级hd两男一女| www.爱久久.com| 黄色录像一级片| 色国产精品一区在线观看| 亚洲男女视频在线观看| www.日韩.com| 国产亚洲一区二区手机在线观看 | 亚洲国产婷婷| 奇米精品一区二区三区| 国产在线看一区| 99久久99久久精品免费看小说. | 亚洲国产91精品在线观看| 91精品专区| 日韩av毛片网| 成人在线黄色| 久久久com| 亚洲欧洲午夜| 岛国精品一区二区三区| 1024成人网色www| 天天操天天干天天摸| 日韩高清欧美高清| 成人激情电影在线看| 最近2019中文字幕大全第二页 | 中文字幕在线播放一区| 亚洲精品一二三| 国产一区二区三区中文字幕 | 中国美女乱淫免费看视频| 亚洲国产另类精品专区| 天堂网一区二区三区| 欧洲国内综合视频| 国产美女www爽爽爽视频| 亚洲一区二区精品| av小次郎在线| 成人精品aaaa网站| 午夜欧美在线| 欧美 日韩 激情| 成人aa视频在线观看| 国产精品美女毛片真酒店| 精品国产三级电影在线观看| 深夜国产在线播放| 欧美做受高潮电影o| 色狠狠久久av综合| 亚洲一区免费看| 卡一卡二国产精品 | 欧美日韩在线一| 91色porny蝌蚪| 精品国产xxx| 日韩美女视频一区二区在线观看| 永久免费网站在线| 成人高清在线观看| 国产日韩综合| 欧美日韩高清丝袜| 欧美人妇做爰xxxⅹ性高电影| 欧美高清视频| 成人av网站观看| 国产精品美女久久久| 成人在线手机视频| 午夜精品福利在线| 欧美女子与性| 国产精选久久久久久| 欧美日本不卡高清| jizz日本免费| 欧美性色黄大片| 日韩av资源| 国产精品一区二区三区毛片淫片| 国产精品久久久久无码av| 中文字幕永久免费| 欧美性精品220| 最新国产在线观看| 99热在线播放| 免费欧美在线| 国产草草浮力影院| 欧美亚洲国产怡红院影院| 麻豆免费在线观看| 精品国产乱码久久久久| 三级一区在线视频先锋| 国产精品无码专区| 在线观看亚洲a| 欧美xxxx做受欧美88bbw| 成人妇女淫片aaaa视频| 国内精品美女在线观看| 国产美女喷水视频| 91精品国产综合久久久久久久| www.中文字幕久久久| 亚洲a在线播放| 老司机精品视频网站| 在线免费观看亚洲视频| 亚洲欧洲视频在线| 成人午夜三级| 亚洲精品手机在线观看| 欧美三级欧美成人高清www|