1. 程式人生 > >《MySQL技術內幕:InnoDB儲存引擎(第2版)》書摘

《MySQL技術內幕:InnoDB儲存引擎(第2版)》書摘

MySQL技術內幕:InnoDB儲存引擎(第2版)

姜承堯

第1章 MySQL體系結構和儲存引擎

>> 在上述例子中使用了mysqld_safe命令來啟動資料庫,當然啟動MySQL例項的方法還有很多,在各種平臺下的方式可能又會有所不同。

>> 當啟動例項時,MySQL資料庫會去讀取配置檔案,根據配置檔案的引數來啟動資料庫例項。這與Oracle的引數檔案(spfile)相似,不同的是,Oracle中如果沒有引數檔案,在啟動例項時會提示找不到該引數檔案,資料庫啟動失敗。而在MySQL資料庫中,可以沒有配置檔案,在這種情況下,MySQL會按照編譯時的預設引數設定啟動例項

>> 從概念上來說,資料庫是檔案的集合,是依照某種資料模型組織起來並存放於二級儲存器中的資料集合;資料庫例項是程式,是位於使用者與作業系統之間的一層資料管理軟體,使用者對資料庫資料的任何操作,包括資料庫定義、資料查詢、資料維護、資料庫執行控制等都是在資料庫例項下進行的,應用程式只有通過資料庫例項才能和資料庫打交道。

>> 需要特別注意的是,儲存引擎是基於表的,而不是資料庫。

>> 關於NDB儲存引擎,有一個問題值得注意,那就是NDB儲存引擎的連線操作(JOIN)是在MySQL資料庫層完成的,而不是在儲存引擎層完成的

>> 相信在任何一本關於資料庫原理的書中,可能都會提到資料庫與傳統檔案系統的最大區別在於資料庫是支援事務的

>> MySQL提供了一個非常好的用來演示MySQL各項功能的示例資料庫,如SQL Server提供的AdventureWorks示例資料庫和Oracle提供的示例資料庫。據我所知,知道MySQL示例資料庫的人很少,可能是因為這個示例資料庫沒有在安裝的時候提示使用者是否安裝(如Oracle和SQL Server)以及這個示例資料庫的下載竟然和文件放在一起

>> 在Linux和UNIX環境下,還可以使用UNIX域套接字。UNIX域套接字其實不是一個網路協議,所以只能在MySQL客戶端和資料庫例項在一臺伺服器上的情況下使用。使用者可以在配置檔案中指定套接字檔案的路徑,如--socket=/tmp/mysql.sock。當資料庫例項啟動後,使用者可以通過下列命令來進行UNIX域套接字檔案的查詢:

第2章 InnoDB儲存引擎

>> 從MySQL資料庫的官方手冊可得知,著名的Internet新聞站點Slashdot.org執行在InnoDB上。Mytrix、Inc.在InnoDB上儲存超過1 TB的資料,還有一些其他站點在InnoDB上處理插入/更新操作的速度平均為800次/秒。這些都證明了InnoDB是一個高效能、高可用、高可擴充套件的儲存引擎。

>> 後臺執行緒的主要作用是負責重新整理記憶體池中的資料,保證緩衝池中的記憶體快取的是最近的資料。此外將已修改的資料檔案重新整理到磁碟檔案,同時保證在資料庫發生異常的情況下InnoDB能恢復到正常執行狀態。

>> 在InnoDB儲存引擎中大量使用了AIO(Async IO)來處理寫IO請求,這樣可以極大提高資料庫的效能。而IO Thread的工作主要是負責這些IO請求的回撥(call back)處理

>> 可以通過命令SHOW ENGINE INNODB STATUS來觀察InnoDB中的IO Thread:

>> 具體來看,緩衝池中快取的資料頁型別有:索引頁、資料頁、undo頁、插入緩衝(insert buffer)、自適應雜湊索引(adaptive hash index)、InnoDB儲存的鎖資訊(lock info)、資料字典資訊(data dictionary)等

>> 從InnoDB 1.0.x版本開始,允許有多個緩衝池例項。每個頁根據雜湊值平均分配到不同緩衝池例項中。這樣做的好處是減少資料庫內部的資源競爭,增加資料庫的併發處理能力。可以通過引數innodb_buffer_pool_instances來進行配置,該值預設為1。

>> 從MySQL 5.6版本開始,還可以通過information_schema架構下的表INNODB_BUFFER_POOL_STATS來觀察緩衝的狀態

>> 在InnoDB儲存引擎中,緩衝池中頁的大小預設為16KB,同樣使用LRU演算法對緩衝池進行管理

>> 。稍有不同的是InnoDB儲存引擎對傳統的LRU演算法做了一些優化。在InnoDB的儲存引擎中,LRU列表中還加入了midpoint位置。新讀取到的頁,雖然是最新訪問的頁,但並不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。這個演算法在InnoDB儲存引擎下稱為midpoint insertion strategy。

>> 那為什麼不採用樸素的LRU演算法,直接將讀取的頁放入到LRU列表的首部呢?這是因為若直接將讀取到的頁放入到LRU的首部,那麼某些SQL操作可能會使緩衝池中的頁被刷新出,從而影響緩衝池的效率。

>> 常見的這類操作為索引或資料的掃描操作。這類操作需要訪問表中的許多頁,甚至是全部的頁,而這些頁通常來說又僅在這次查詢操作中需要,並不是活躍的熱點資料。如果頁被放入LRU列表的首部,那麼非常可能將所需要的熱點資料頁從LRU列表中移除,而在下一次需要讀取該頁時,InnoDB儲存引擎需要再次訪問磁碟。

>> Buffer pool hit rate,表示緩衝池的命中率,這個例子中為100%,說明緩衝池執行狀態非常良好。通常該值不應該小於95%。若發生Buffer pool hit rate的值小於95%這種情況,使用者需要觀察是否是由於全表掃描引起的LRU列表被汙染的問題。

>> 執行命令SHOW ENGINE INNODB STATUS顯示的不是當前的狀態,而是過去某個時間範圍內InnoDB儲存引擎的狀態。從上面的例子可以發現,Per second averages calculated from the last 24 seconds代表的資訊為過去24秒內的資料庫狀態。

>> 在LRU列表中的頁被修改後,稱該頁為髒頁(dirty page),即緩衝池中的頁和磁碟上的頁的資料產生了不一致。這時資料庫會通過CHECKPOINT機制將髒頁重新整理回磁碟,而Flush列表中的頁即為髒頁列表。需要注意的是,髒頁既存在於LRU列表中,也存在於Flush列表中

>> 重做日誌緩衝一般不需要設定得很大,因為一般情況下每一秒鐘會將重做日誌緩衝重新整理到日誌檔案,因此使用者只需要保證每秒產生的事務量在這個緩衝大小之內即可

>> 當前3TB的MySQL資料庫已並不少見,但是3 TB的記憶體卻非常少見。目前Oracle Exadata旗艦資料庫一體機也就只有2 TB的記憶體。

>> 因此Checkpoint(檢查點)技術的目的是解決以下幾個問題:□ 縮短資料庫的恢復時間;□ 緩衝池不夠用時,將髒頁重新整理到磁碟;□ 重做日誌不可用時,重新整理髒頁。

>> 對於InnoDB儲存引擎而言,其是通過LSN(Log Sequence Number)來標記版本的。而LSN是8位元組的數字,其單位是位元組。每個頁有LSN,重做日誌中也有LSN,Checkpoint也有LSN。可以通過命令SHOW ENGINE INNODB STATUS來觀察:

>> InnoDB儲存引擎的關鍵特性包括:□ 插入緩衝(Insert Buffer)□ 兩次寫(Double Write)□ 自適應雜湊索引(Adaptive Hash Index)□ 非同步IO(Async IO)□ 重新整理鄰接頁(Flush Neighbor Page)

>> InnoDB儲存引擎開創性地設計了Insert Buffer,對於非聚集索引的插入或更新操作,不是每一次直接插入到索引頁中,而是先判斷插入的非聚集索引頁是否在緩衝池中,若在,則直接插入;若不在,則先放入到一個Insert Buffer物件中,好似欺騙。資料庫這個非聚集的索引已經插到葉子節點,而實際並沒有,只是存放在另一個位置。

>> 然後再以一定的頻率和情況進行Insert Buffer和輔助索引頁子節點的merge(合併)操作,這時通常能將多個插入合併到一個操作中(因為在一個索引頁中),這就大大提高了對於非聚集索引插入的效能。

>> 輔助索引不能是唯一的,因為在插入緩衝時,資料庫並不去查詢索引頁來判斷插入的記錄的唯一性。如果去查詢肯定又會有離散讀取的情況發生,從而導致Insert Buffer失去了意

>> 正如前面所說的,目前Insert Buffer存在一個問題是:在寫密集的情況下,插入緩衝會佔用過多的緩衝池記憶體(innodb_buffer_pool),預設最大可以佔用到1/2的緩衝池記憶體。

>> InnoDB從1.0.x版本開始引入了Change Buffer,可將其視為Insert Buffer的升級。從這個版本開始,InnoDB儲存引擎可以對DML操作——INSERT、DELETE、UPDATE都進行緩衝,他們分別是:Insert Buffer、Delete Buffer、Purge buffer。

>> innodb_change_buffer_max_size值預設為25,表示最多使用1/4的緩衝池記憶體空間。而需要注意的是,該引數的最大有效值為50。

>> 可能令絕大部分使用者感到吃驚的是,Insert Buffer的資料結構是一棵B+樹。在MySQL 4.1之前的版本中每張表有一棵Insert Buffer B+樹。而在現在的版本中,全域性只有一棵Insert Buffer B+樹,負責對所有的表的輔助索引進行Insert Buffer。

>> 而這棵B+樹存放在共享表空間中,預設也就是ibdata1中。因此,試圖通過獨立表空間ibd檔案恢復表中資料時,往往會導致CHECK TABLE失敗

>> 。這是因為表的輔助索引中的資料可能還在Insert Buffer中,也就是共享表空間中,所以通過ibd檔案進行恢復後,還需要進行REPAIR TABLE操作來重建表上所有的輔助索引。

>> 如果說Insert Buffer帶給InnoDB儲存引擎的是效能上的提升,那麼doublewrite(兩次寫)帶給InnoDB儲存引擎的是資料頁的可靠性。

>> 當發生資料庫宕機時,可能InnoDB儲存引擎正在寫入某個頁到表中,而這個頁只寫了一部分,比如16KB的頁,只寫了前4KB,之後就發生了宕機,這種情況被稱為部分寫失效(partial page write)。在InnoDB儲存引擎未使用doublewrite技術前,曾經出現過因為部分寫失效而導致資料丟失的情況。有經驗的DBA也許會想,如果發生寫失效,可以通過重做日誌進行恢復。這是一個辦法。但是必須清楚地認識到,重做日誌中記錄的是對頁的物理操作,如偏移量800,寫'aaaa'記錄。如果這個頁本身已經發生了損壞,再對其進行重做是沒有意義的。這就是說,在應用(apply)重做日誌前,使用者需要一個頁的副本,當寫入失效發生時,先通過頁的副本來還原該頁,再進行重做,這就是doublewrite

>> 在對緩衝池的髒頁進行重新整理時,並不直接寫磁碟,而是會通過memcpy函式將髒頁先複製到記憶體中的doublewrite buffer,之後通過doublewrite buffer再分兩次,每次1MB順序地寫入共享表空間的物理磁碟上,然後馬上呼叫fsync函式,同步磁碟,避免緩衝寫帶來的問題在這個過程中,因為doublewrite頁是連續的,因此這個過程是順序寫的,開銷並不是很大。在完成doublewrite頁的寫入後,再將doublewrite buffer中的頁寫入各個表空間檔案中,此時的寫入則是離散的。

>> InnoDB儲存引擎會監控對錶上各索引頁的查詢。如果觀察到建立雜湊索引可以帶來速度提升,則建立雜湊索引,稱之為自適應雜湊索引(Adaptive Hash Index,AHI)。

>> 值得注意的是,雜湊索引只能用來搜尋等值的查詢,如SELECT*FROM table WHERE index_col='xxx'。而對於其他查詢型別,如範圍查詢,是不能使用雜湊索引的,因此這裡出現了non-hash searches/s的情況

>> 使用者可以在發出一個IO請求後立即再發出另一個IO請求,當全部IO請求傳送完畢後,等待所有IO操作的完成,這就是AIO。AIO的另一個優勢是可以進行IO Merge操作,也就是將多個IO合併為1個IO,這樣可以提高IOPS的效能

>> 需要注意的是,Native AIO需要作業系統提供支援。Windows系統和Linux系統都提供Native AIO支援,而Mac OSX系統則未提供

>> 引數innodb_use_native_aio用來控制是否啟用Native AIO,在Linux作業系統下,預設值為ON

>> 在關閉時,引數innodb_fast_shutdown影響著表的儲存引擎為InnoDB的行為。該引數可取值為0、1、2,預設值為1。

>> 引數innodb_force_recovery影響了整個InnoDB儲存引擎恢復的狀況。該引數值預設為0,代表當發生需要恢復時,進行所有的恢復操作,當不能進行有效恢復時,如資料頁發生了corruption,MySQL資料庫可能發生宕機(crash),並把錯誤寫入錯誤日誌中去。

>> 引數innodb_force_recovery還可以設定為6個非零值:1~6。大的數字表示包含了前面所有小數字表示的影響

第3章 檔案

>> 預設情況下,MySQL例項會按照一定的順序在指定的位置進行讀取,使用者只需通過命令mysql--help | grep my.cnf來尋找即可。

>> Oracle資料庫存在所謂的隱藏引數(undocumented parameter),以供Oracle“內部人士”使用,SQL Server也有類似的引數。有些DBA曾問我,MySQL中是否也有這類引數。我的回答是:沒有,也不需要。即使Oracle和SQL Server中都有些所謂的隱藏引數,在絕大多數的情況下,這些資料庫廠商也不建議使用者在生產環境中對其進行很大的調整。

>> MySQL資料庫中的引數可以分為兩類:□ 動態(dynamic)引數□ 靜態(static)引數動態引數意味著可以在MySQL例項執行中進行更改,靜態引數說明在整個例項生命週期內都不得進行更改,就好像是隻讀(read only)的

>> 當出現MySQL資料庫不能正常啟動時,第一個必須查詢的檔案應該就是錯誤日誌檔案,該檔案記錄了錯誤資訊,能很好地指導使用者發現問題。

>> 設定long_query_time這個閾值後,MySQL資料庫會記錄執行時間超過該值的所有SQL語句,但執行時間正好等於long_query_time的情況並不會被記錄下。也就是說,在原始碼中判斷的是大於long_query_time,而非大於等於

>> 另一個和慢查詢日誌有關的引數是log_queries_not_using_indexes,如果執行的SQL語句沒有使用索引,則MySQL資料庫同樣會將這條SQL語句記錄到慢查詢日誌檔案。

>> MySQL 5.6.5版本開始新增了一個引數log_throttle_queries_not_using_indexes,用來表示每分鐘允許記錄到slow log的且未使用索引的SQL語句次數。該值預設為0,表示沒有限制。在生產環境下,若沒有使用索引,此類SQL語句會頻繁地被記錄到slow log,從而導致slow log檔案的大小不斷增加,故DBA可通過此引數進行配置。

>> MySQL 5.1開始可以將慢查詢的日誌記錄放入一張表中,這使得使用者的查詢更加方便和直觀。慢查詢表在mysql架構下,名為slow_log

>> 檢視slow_log表的定義會發現該表使用的是CSV引擎,對大資料量下的查詢效率可能不高。使用者可以把slow_log表的引擎轉換到MyISAM,並在start_time列上新增索引以進一步提高查詢的效率。

>> 不能忽視的是,將slow_log表的儲存引擎更改為MyISAM後,還是會對資料庫造成額外的開銷。

>> 使用者可以通過額外的引數long_query_io將超過指定邏輯IO次數的SQL語句記錄到slow log中。該值預設為100,即表示對於邏輯讀取次數大於100的SQL語句,記錄到slow log中。而為了相容原MySQL資料庫的執行方式,還添加了引數slow_query_type,用來表示啟用slow log的方式

>> 查詢日誌記錄了所有對MySQL資料庫請求的資訊,無論這些請求是否得到了正確的執行。預設檔名為:主機名.log

>> 。同樣地,從MySQL 5.1開始,可以將查詢日誌的記錄放入mysql架構下的general_log表中,該表的使用方法和前面小節提到的slow_log基本一樣

>> 二進位制日誌(binary log)記錄了對MySQL資料庫執行更改的所有操作,但是不包括SELECT和SHOW這類操作,因為這類操作對資料本身並沒有修改。

>> 使用事務的表儲存引擎(如InnoDB儲存引擎)時,所有未提交(uncommitted)的二進位制日誌會被記錄到一個快取中去,等該事務提交(committed)時直接將緩衝中的二進位制日誌寫入二進位制日誌檔案,而該緩衝的大小由binlog_cache_size決定,預設大小為32K。

>> 此外,binlog_cache_size是基於會話(session)的,也就是說,當一個執行緒開始一個事務時,MySQL會自動分配一個大小為binlog_cache_size的快取,因此該值的設定需要相當小心,不能設定過大。當一個事務的記錄大於設定的binlog_cache_size時,MySQL會把緩衝中的日誌寫入一個臨時檔案中,因此該值又不能設得太小

>> Binlog_cache_use記錄了使用緩衝寫二進位制日誌的次數,binlog_cache_disk_use記錄了使用臨時檔案寫二進位制日誌的次數

>> 預設情況下,二進位制日誌並不是在每次寫的時候同步到磁碟(使用者可以理解為緩衝寫)。因此,當資料庫所在作業系統發生宕機時,可能會有最後一部分資料沒有寫入二進位制日誌檔案中,這會給恢復和複製帶來問題

>> 即使將sync_binlog設為1,還是會有一種情況導致問題的發生。當使用InnoDB儲存引擎時,在一個事務發出COMMIT動作之前,由於sync_binlog為1,因此會將二進位制日誌立即寫入磁碟。如果這時已經寫入了二進位制日誌,但是提交還沒有發生,並且此時發生了宕機,那麼在MySQL資料庫下次啟動時,由於COMMIT操作並沒有發生,這個事務會被回滾掉。但是二進位制日誌已經記錄了該事務資訊,不能被回滾。

>> 如果當前資料庫是複製中的slave角色,則它不會將從master取得並執行的二進位制日誌寫入自己的二進位制日誌檔案中去。如果需要寫入,要設定log-slave-update。如果需要搭建master=>slave=>slave架構的複製,則必須設定該引數。

>> MySQL 5.1開始引入了binlog_format引數,該引數可設的值有STATEMENT、ROW和MIXED

>> 上面的這個例子告訴我們,將引數binlog_format設定為ROW,會對磁碟空間要求有一定的增加。而由於複製是採用傳輸二進位制日誌方式實現的,因此複製的網路開銷也有所增加。

>> 要檢視二進位制日誌檔案的內容,必須通過MySQL提供的工具mysqlbinlog。對於STATEMENT格式的二進位制日誌檔案,在使用mysqlbinlog後,看到的就是執行的邏輯SQL語句

>> 但不論表採用何種儲存引擎,MySQL都有一個以frm為字尾名的檔案,這個檔案記錄了該表的表結構定義。

>> frm還用來存放檢視的定義,如使用者建立了一個v_a檢視,那麼對應地會產生一個v_a.frm檔案,用來記錄檢視的定義,該檔案是文字檔案,可以直接使用cat命令進行檢視

>> 設定innodb_data_file_path引數後,所有基於InnoDB儲存引擎的表的資料都會記錄到該共享表空間中。若設定了引數innodb_file_per_table,則使用者可以將每個基於InnoDB儲存引擎的表產生一個獨立表空間

>> 。獨立表空間的命名規則為:表名.ibd。通過這樣的方式,使用者不用將所有資料都存放於預設的表空間

>> 這些單獨的表空間檔案僅儲存該表的資料、索引和插入緩衝BITMAP等資訊,其餘資訊還是存放在預設的表空間中

>> 在預設情況下,在InnoDB儲存引擎的資料目錄下會有兩個名為ib_logfile0和ib_logfile1的檔案。在MySQL官方手冊中將其稱為InnoDB儲存引擎的日誌檔案,不過更準確的定義應該是重做日誌檔案(redo log file)。為什麼強調是重做日誌檔案呢?因為重做日誌檔案對於InnoDB儲存引擎至關重要,它們記錄了對於InnoDB儲存引擎的事務日誌。

>> 每個InnoDB儲存引擎至少有1個重做日誌檔案組(group),每個檔案組下至少有2個重做日誌檔案,如預設的ib_logfile0和ib_logfile1。為了得到更高的可靠性,使用者可以設定多個的映象日誌組(mirrored log groups),將不同的檔案組放在不同的磁碟上,以此提高重做日誌的高可用性。在日誌組中每個重做日誌檔案的大小一致,並以迴圈寫入的方式執行。

>> InnoDB儲存引擎先寫重做日誌檔案1,當達到檔案的最後時,會切換至重做日誌檔案2,再當重做日誌檔案2也被寫滿時,會再切換到重做日誌檔案1中。

>> 若磁碟本身已經做了高可用的方案,如磁碟陣列,那麼可以不開啟重做日誌映象的功能

>> 二進位制日誌會記錄所有與MySQL資料庫有關的日誌記錄,包括InnoDB、MyISAM、Heap等其他儲存引擎的日誌。而InnoDB儲存引擎的重做日誌只記錄有關該儲存引擎本身的事務日誌。

>> 其次,記錄的內容不同,無論使用者將二進位制日誌檔案記錄的格式設為STATEMENT還是ROW,又或者是MIXED,其記錄的都是關於一個事務的具體操作內容,即該日誌是邏輯日誌。而InnoDB儲存引擎的重做日誌檔案記錄的是關於每個頁(Page)的更改的物理情況。

>> 此外,寫入的時間也不同,二進位制日誌檔案僅在事務提交前進行提交,即只寫磁碟一次,不論這時該事務多大。而在事務進行的過程中,卻不斷有重做日誌條目(redo entry)被寫入到重做日誌檔案中。

>> 在InnoDB儲存引擎中,對於各種不同的操作有著不同的重做日誌格式。到InnoDB 1.2.x版本為止,總共定義了51種重做日誌型別。

>> 在第2章中已經提到,寫入重做日誌檔案的操作不是直接寫,而是先寫入一個重做日誌緩衝(redo log buffer)中,然後按照一定的條件順序地寫入日誌檔案

>> 從重做日誌緩衝往磁碟寫入時,是按512個位元組,也就是一個扇區的大小進行寫入。因為扇區是寫入的最小單位,因此可以保證寫入必定是成功的。因此在重做日誌的寫入過程中不需要有doublewrite。

>> 因此為了保證事務的ACID中的永續性,必須將innodb_flush_log_at_trx_commit設定為1,也就是每當有事務提交時,就必須確保事務都已經寫入重做日誌檔案。那麼當資料庫因為意外發生宕機時,可以通過重做日誌檔案恢復,並保證可以恢復已經提交的事務。

第4章 表

>> 在InnoDB儲存引擎中,表都是根據主鍵順序組織存放的,這種儲存方式的表稱為索引組織表(index organized table)。

>> 在InnoDB儲存引擎表中,每張表都有個主鍵(Primary Key),如果在建立表時沒有顯式地定義主鍵,則InnoDB儲存引擎會按如下方式選擇或建立主鍵:□ 首先判斷表中是否有非空的唯一索引(Unique NOT NULL),如果有,則該列即為主鍵。□ 如果不符合上述條件,InnoDB儲存引擎自動建立一個6位元組大小的指標

>> 當表中有多個非空唯一索引時,InnoDB儲存引擎將選擇建表時第一個定義的非空唯一索引為主鍵

>> 。這裡需要非常注意的是,主鍵的選擇根據的是定義索引的順序,而不是建表時列的順序。

>> 可以通過下面的SQL語句判斷表的主鍵值:

>> _rowid可以顯示錶的主鍵,因此通過上述查詢可以找到表z的主鍵

>> 另外需要注意的是,_rowid只能用於檢視單個列為主鍵的情況,對於多列組成的主鍵就顯得無能為力了

>> 從InnoDB儲存引擎的邏輯儲存結構看,所有資料都被邏輯地存放在一個空間中,稱之為表空間(tablespace)。表空間又由段(segment)、區(extent)、頁(page)組成。頁在一些文件中有時也稱為塊(block),

>> InnoDB儲存引擎的邏輯儲存結構大致如圖4-1所示。

>> 第3章中已經介紹了在預設情況下InnoDB儲存引擎有一個共享表空間ibdata1,即所有資料都存放在這個表空間內。如果使用者啟用了引數innodb_file_per_table,則每張表內的資料可以單獨放到一個表空間內。

>> 如果啟用了innodb_file_per_table的引數,需要注意的是每張表的表空間記憶體放的只是資料、索引和插入緩衝Bitmap頁其他類的資料,如回滾(undo)資訊,插入緩衝索引頁、系統事務資訊,二次寫緩衝(Double write buffer)等還是存放在原來的共享表空間內。這同時也說明了另一個問題:即使在啟用了引數innodb_file_per_table之後,共享表空間還是會不斷地增加其大小。

>> InnoDB儲存引擎不會在執行rollback時去收縮這個表空間。雖然InnoDB不會回收這些空間,但是會自動判斷這些undo資訊是否還需要,如果不需要,則會將這些空間標記為可用空間,供下次undo使用。

>> 我用python寫了一個py_innodb_page_info小工具,用來查看錶空間中各頁的型別和資訊,使用者可以在code.google.com上搜索david-mysql-tools進行查詢

>> 因為前面已經介紹過了InnoDB儲存引擎表是索引組織的(index organized),因此資料即索引,索引即資料。那麼資料段即為B+樹的葉子節點(圖4-1的Leaf node segment),索引段即為B+樹的非索引節點(圖4-1的Non-leaf node segment)。

>> 區是由連續頁組成的空間,在任何情況下每個區的大小都為1MB。為了保證區中頁的連續性,InnoDB儲存引擎一次從磁碟申請4~5個區。在預設情況下,InnoDB儲存引擎頁的大小為16KB,即一個區中一共有64個連續的頁。

>> InnoDB 1.0.x版本開始引入壓縮頁,即每個頁的大小可以通過引數KEY_BLOCK_SIZE設定為2K、4K、8K,因此每個區對應頁的數量就應該為512、256、128。

>> InnoDB 1.2.x版本新增了引數innodb_page_size,通過該引數可以將預設頁的大小設定為4K、8K,但是頁中的資料庫不是壓縮。這時區中頁的數量同樣也為256、128。總之,不論頁的大小怎麼變化,區的大小總是為1M。

>> 在使用者啟用了引數innodb_file_per_talbe後,建立的表預設大小是96KB。區中是64個連續的頁,建立的表的大小至少是1MB才對啊?其實這是因為在每個段開始時,先用32個頁大小的碎片頁(fragment page)來存放資料,在使用完這些頁之後才是64個連續頁的申請。這樣做的目的是,對於一些小表,或者是undo這類的段,可以在開始時申請較少的空間,節省磁碟容量的開銷

>> 因為已經用完了32個碎片頁,新的頁會採用區的方式進行空間的申請,如果此時使用者再通過py_innodb_page_info工具來看錶空間檔案t1.ibd,應該可以看到很多型別為Freshly Allocated Page的頁:

>> 從InnoDB 1.2.x版本開始,可以通過引數innodb_page_size將頁的大小設定為4K、8K、16K。若設定完成,則所有表中頁的大小都為innodb_page_size,不可以對其再次進行修改。除非通過mysqldump匯入和匯出操作來產生新的庫

>> 每個頁存放的行記錄也是有硬性定義的,最多允許存放16KB / 2-200行的記錄,即7992行記錄

>> InnoDB儲存引擎提供了Compact和Redundant兩種格式來存放行記錄資料,

>> 在MySQL 5.1版本中,預設設定為Compact行格式。使用者可以通過命令SHOW TABLE STATUS LIKE 'table_name'來檢視當前表使用的行格式,其中row_format屬性表示當前所使用的行記錄結構型別。

>> 每行資料除了使用者定義的列外,還有兩個隱藏列,事務ID列和回滾指標列,分別為6位元組和7位元組的大小。

>> 若InnoDB表沒有定義主鍵,每行還會增加一個6位元組的rowid列。

>> InnoDB儲存引擎可以將一條記錄中的某些資料儲存在真正的資料頁面之外。一般認為BLOB、LOB這類的大物件列型別的儲存會把資料存放在資料頁面之外。但是,這個理解有點偏差,BLOB可以不將資料放在溢位頁面,而且即便是VARCHAR列資料型別,依然有可能被存放為行溢位資料

>> 從錯誤訊息可以看到InnoDB儲存引擎並不支援65535長度的VARCHAR。這是因為還有別的開銷,通過實際測試發現能存放VARCHAR型別的最大長度為65532。

>> 因此從這個例子中使用者也應該理解VARCHAR(N)中的N指的是字元的長度。而文件中說明VARCHAR型別最大支援65535,單位是位元組。

>> 此外需要注意的是,MySQL官方手冊中定義的65535長度是指所有VARCHAR列的長度總和,如果列的長度總和超出這個長度,依然無法建立

>> 即使能存放65532個位元組,但是有沒有想過,InnoDB儲存引擎的頁為16KB,即16384位元組,怎麼能存放65532位元組呢?因此,在一般情況下,InnoDB儲存引擎的資料都是存放在頁型別為B-tree node中。但是當發生行溢位時,資料存放在頁型別為Uncompress BLOB頁中。

>> InnoDB儲存引擎表是索引組織的,即B+Tree的結構,這樣每個頁中至少應該有兩條行記錄(否則失去了B+Tree的意義,變成連結串列了)。因此,如果頁中只能存放下一條記錄,那麼InnoDB儲存引擎會自動將行資料存放到溢位頁中

>> 經過多次試驗測試,發現這個閾值的長度為8098

>> 對於TEXT或BLOB的資料型別,使用者總是以為它們是存放在Uncompressed BLOB Page中的,其實這也是不準確的。是放在資料頁中還是BLOB頁中,和前面討論的VARCHAR一樣,至少保證一個頁能存放兩條記錄

>> InnoDB 1.0.x版本開始引入了新的檔案格式(file format,使用者可以理解為新的頁格式),以前支援的Compact和Redundant格式稱為Antelope檔案格式,新的檔案格式稱為Barracuda檔案格式。Barracuda檔案格式下擁有兩種新的行記錄格式:Compressed和Dynamic。

>> 從MySQL 4.1版本開始,CHR(N)中的N指的是字元的長度,而不是之前版本的位元組長度。也就說在不同的字符集下,CHAR型別列內部儲存的可能不是定長的資料

>> SELECT a,CHAR_LENGTH(a),LENGTH(a)

>> SELECT a,HEX(a)        -> FROM j\G;

>> SHOW VARIABLES LIKE 'innodb_file_format'\G;

>> 關係型資料庫系統和檔案系統的一個不同點是,關係資料庫本身能保證儲存資料的完整性,不需要應用程式的控制,而檔案系統一般需要在程式端進行控制

>> 通過設定引數sql_mode的值為STRICT_TRANS_TABLES,這次MySQL資料庫對於輸入值的合法性進行了約束,而且針對不同的錯誤,提示的錯誤內容也都不同。引數sql_mode可設的值有很多,具體可參考MySQL官方手冊。

>> 最多可以為一個表建立6個觸發器,即分別為INSERT、UPDATE、DELETE的BEFORE和AFTER各定義一個。

>> 假設有張使用者消費表,每次使用者購買一樣物品後其金額都是減的,若這時有“不懷好意”的使用者做了類似減去一個負值的操作,這樣使用者的錢沒減少反而會不斷增加

>> 一般來說,稱被引用的表為父表,引用的表稱為子表。外來鍵定義時的ON DELETE和ON UPDATE表示在對父表進行DELETE和UPDATE操作時,對子表所做的操作

>> CASCADE表示當父表發生DELETE或UPDATE操作時,對相應的子表中的資料也進行DELETE或UPDATE操作。SET NULL表示當父表發生DELETE或UPDATE操作時,相應的子表中的資料被更新為NULL值,但是子表中相對應的列必須允許為NULL值。NO ACTION表示當父表發生DELETE或UPDATE操作時,丟擲錯誤,不允許這類操作發生。RESTRICT表示當父表發生DELETE或UPDATE操作時,丟擲錯誤,不允許這類操作發生。

>> 在其他資料庫中,如Oracle資料庫,有一種稱為延時檢查(deferred check)的外來鍵約束,即檢查在SQL語句執行完成後再進行。而目前MySQL資料庫的外來鍵約束都是即時檢查(immediate check)

>> 雖然檢視是基於基表的一個虛擬表,但是使用者可以對某些檢視進行更新操作,其本質就是通過檢視的定義來更新基本表。一般稱可以進行更新操作的檢視為可更新檢視(updatable view)。檢視定義中的WITH CHECK OPTION就是針對於可更新的檢視的,即更新的值是否需要檢查

>> MySQL資料庫DBA的一個常用的命令是SHOW TABLES,該命令會顯示出當前資料庫下所有的表。但因為檢視是虛表,同樣被作為表顯示出來

>> 使用者只想檢視當前架構下的基表,可以通過information_schema架構下的TABLE表來查詢,並搜尋表型別為BASE TABLE的表

>> Oracle資料庫支援物化檢視——該檢視不是基於基表的虛表,而是根據基表實際存在的實表,即物化檢視的資料儲存在非易失的儲存裝置上。物化檢視可以用於預先計算並儲存多表的連結(JOIN)或聚集(GROUP BY)等耗時較多的SQL操作結果。這樣,在執行復雜查詢時,就可以避免進行這些耗時的操作,從而快速得到結果。物化檢視的好處是對於一些複雜的統計類查詢能直接查出結果。在Microsoft SQL Server資料庫中,稱這種檢視為索引檢視。

>> 分割槽功能並不是在儲存引擎層完成的,因此不是隻有InnoDB儲存引擎支援分割槽,常見的儲存引擎MyISAM、NDB等都支援。但也並不是所有的儲存引擎都支援,如CSV、FEDORATED、MERGE等就不支援。在使用分割槽功能前,應該對選擇的儲存引擎對分割槽的支援有所瞭解。

>> MySQL資料庫支援的分割槽型別為水平分[插圖],並不支援垂直分[插圖]。此外,MySQL資料庫的分割槽是區域性分割槽索引,一個分割槽中既存放了資料又存放了索引。而全域性分割槽是指,資料存放在各個分割槽中,但是所有資料的索引放在一個物件中。目前,MySQL資料庫還不支援全域性分割槽。

>> 不論建立何種型別的分割槽,如果表中存在主鍵或唯一索引時,分割槽列必須是唯一索引的一個組成部分

>> 唯一索引可以是允許NULL值的,並且分割槽列只要是唯一索引的一個組成部分,不需要整個唯一索引列都是分割槽列

>> 如果建表時沒有指定主鍵,唯一索引,可以指定任何一個列為分割槽列

>> 查看錶在磁碟上的物理檔案,啟用分割槽之後,表不再由一個ibd檔案組成了,而是由建立分割槽時的各個分割槽ibd檔案組成,如下面的t#P#p0.ibd,t#P#p1.ibd

>> 通過EXPLAIN PARTITION命令我們可以發現,在上述語句中,SQL優化器只需要去搜索p2008這個分割槽,而不會去搜索所有的分割槽——稱為Partition Pruning(分割槽修剪),故查詢的速度得到了大幅度的提

>> 在前面介紹的RANGE、LIST、HASH和KEY這四種分割槽中,分割槽的條件是:資料必須是整型(interger),如果不是整型,那應該需要通過函式將其轉化為整型,如YEAR(),TO_DAYS(),MONTH()等函式。MySQL5.5版本開始支援COLUMNS分割槽,可視為RANGE分割槽和LIST分割槽的一種進化。COLUMNS分割槽可以直接使用非整型的資料進行分割槽,分割槽根據型別直接比較而得,不需要轉化為整型。此外,RANGE COLUMNS分割槽可以對多個列的值進行分割槽。

>> 子分割槽(subpartitioning)是在分割槽的基礎上再進行分割槽,有時也稱這種分割槽為複合分割槽(composite partitioning)。MySQL資料庫允許在RANGE和LIST的分割槽上再進行HASH或KEY的子分割槽

>> 子分割槽的建立需要注意以下幾個問題:□ 每個子分割槽的數量必須相同。□ 要在一個分割槽表的任何分割槽上使用SUBPARTITION來明確定義任何子分割槽,就必須定義所有的子分割槽。

>> 子分割槽可以用於特別大的表,在多個磁碟間分別分配資料和索引

>> MySQL資料庫允許對NULL值做分割槽,但是處理的方法與其他資料庫可能完全不同。MYSQL資料庫的分割槽總是視NULL值視小於任何的一個非NULL值,這和MySQL資料庫中處理NULL值的ORDER BY操作是一樣的

>> 。因此對於不同的分割槽型別,MySQL資料庫對於NULL值的處理也是各不相同。

>> HASH和KEY分割槽對於NULL的處理方式和RANGE分割槽、LIST分割槽不一樣。任何分割槽函式都會將含有NULL值的記錄返回為0

>> 對於OLAP的應用,分割槽的確是可以很好地提高查詢的效能,因為OLAP應用大多數查詢需要頻繁地掃描一張很大的表。假設有一張1億行的表,其中有一個時間戳屬性列。使用者的查詢需要從這張表中獲取一年的資料。如果按時間戳進行分割槽,則只需要掃描相應的分割槽即可。這就是前面介紹的Partition Pruning技術。

>> 然而對於OLTP的應用,分割槽應該非常小心。在這種應用下,通常不可能會獲取一張大表中10%的資料,大部分都是通過索引返回幾條記錄即可。而根據B+樹索引的原理可知,對於一張大表,一般的B+樹需要2~3次的磁碟IO。因此B+樹可以很好地完成操作,不需要分割槽的幫助,並且設計不好的分割槽會帶來嚴重的效能問題。

>> 我發現很多開發團隊會認為含有1000W行的表是一張非常巨大的表,所以他們往往會選擇採用分割槽,如對主鍵做10個HASH的分割槽,這樣每個分割槽就只有100W的資料了,因此查詢應該變得更快了,如SELECT * FROM TABLE WHERE [email protected]。但是有沒有考慮過這樣一種情況:100W和1000W行的資料本身構成的B+樹的層次都是一樣的,可能都是2層。

>> MySQL 5.6開始支援ALTER TABLE … EXCHANGE PARTITION語法。該語句允許分割槽或子分割槽中的資料與另一個非分割槽的表中的資料進行交換。如果非分割槽表中的資料為空,那麼相當於將分割槽中的資料移動到非分割槽表中。若分割槽表中的資料為空,則相當於將外部表中的資料匯入到分割槽中。

第5章 索引與演算法

>>  B+樹中的B不是代表二叉(binary),而是代表平衡(balance),因為B+樹是從最早的平衡二叉樹演化而來,但是B+樹不是一個二叉樹。

>> 另一個常常被DBA忽視的問題是:B+樹索引並不能找到一個給定鍵值的具體行。B+樹索引能找到的只是被查詢資料行所在的頁。然後資料庫通過把頁讀入到記憶體,再在記憶體中進行查詢,最後得到要查詢的資料。

>> 平衡二叉樹的查詢效能是比較高的,但不是最高的,只是接近最高效能。最好的效能需要建立一棵最優二叉樹,但是最優二叉樹的建立和維護需要大量的操作,因此,使用者一般只需建立一棵平衡二叉樹即可。

>> 平衡二叉樹的查詢速度的確很快,但是維護一棵平衡二叉樹的代價是非常大的。通常來說,需要1次或多次左旋和右旋來得到插入或更新後樹的平衡性。

>> B+樹由B樹和索引順序訪問方法(ISAM,是不是很熟悉?對,這也是MyISAM引擎最初參考的資料結構)演化而來,但是在現實使用過程中幾乎已經沒有使用B樹的情況了。

>> B+樹是為磁碟或其他直接存取輔助裝置設計的一種平衡查詢樹。在B+樹中,所有記錄節點都是按鍵值的大小順序存放在同一層的葉子節點上,由各葉子節點指標進行連線。

>> 可以看到,不管怎麼變化,B+樹總是會保持平衡。但是為了保持平衡對於新插入的鍵值可能需要做大量的拆分頁(split)操作。因為B+樹結構主要用於磁碟,頁的拆分意味著磁碟的操作,所以應該在可能的情況下儘量減少頁的拆分操作。

>> 因此,B+樹同樣提供了類似於平衡二叉樹的旋轉(Rotation)功能。

>> B+樹索引的本質就是B+樹在資料庫中的實現。

>> 但是B+索引在資料庫中有一個特點是高扇出性,因此在資料庫中,B+樹的高度一般都在2~4層,這也就是說查詢某一鍵值的行記錄時最多隻需要2到4次IO,這倒不錯。因為當前一般的機械磁碟每秒至少可以做100次IO,2~4次的IO意味著查詢時間只需0.02~0.04秒。

>> 資料庫中的B+樹索引可以分為聚集索引(clustered inex)和輔助索引(secondary index[插圖],但是不管是聚集還是輔助的索引,其內部都是B+樹的,即高度平衡的,葉子節點存放著所有的資料。聚集索引與輔助索引不同的是,葉子節點存放的是否是一整行的資訊。

>> 聚集索引(clustered index)就是按照每張表的主鍵構造一棵B+樹,同時葉子節點中存放的即為整張表的行記錄資料,也將聚集索引的葉子節點稱為資料頁。

>> 由於實際的資料頁只能按照一棵B+樹進行排序,因此每張表只能擁有一個聚集索引

>> 許多資料庫的文件會這樣告訴讀者:聚集索引按照順序物理地儲存資料。如果看圖5-14,可能也會有這樣的感覺。但是試想一下,如果聚集索引必須按照特定順序存放物理記錄,則維護成本顯得非常之高。所以,聚集索引的儲存並不是物理上連續的,而是邏輯上連續的。

>> 對於輔助索引(Secondary Index,也稱非聚集索引),葉子節點並不包含行記錄的全部資料。葉子節點除了包含鍵值以外,每個葉子節點中的索引行中還包含了一個書籤(bookmark)。該書籤用來告訴InnoDB儲存引擎哪裡可以找到與索引相對應的行資料

>> 。由於InnoDB儲存引擎表是索引組織表,因此InnoDB儲存引擎的輔助索引的書籤就是相應行資料的聚集索引鍵。

>> 如果在一棵高度為3的輔助索引樹中查詢資料,那需要對這棵輔助索引樹遍歷3次找到指定主鍵,如果聚集索引樹的高度同樣為3,那麼還需要對聚集索引樹進行3次查詢,最終找到一個完整的行資料所在的頁,因此一共需要6次邏輯IO訪問以得到最終的一個數據頁。

>> 使用者可以設定對整個列的資料進行索引,也可以只索引一個列的開頭部分資料,如前面建立的表t,列b為varchar(8000),但是使用者可以只索引前100個欄位

>>  Cardinality:非常關鍵的值,表示索引中唯一值的數目的估計值。Cardinality表的行數應儘可能接近1,如果非常小,那麼使用者需要考慮是否可以刪除此索引。

>> Cardinality值非常關鍵,優化器會根據這個值來判斷是否使用這個索引。但是這個值並不是實時更新的,即並非每次索引的更新都會更新該值,因為這樣代價太大了

>> Cardinality為NULL,在某些情況下可能會發生索引建立了卻沒有用到的情況。或者對兩條基本一樣的語句執行EXPLAIN,但是最終出來的結果不一樣:一個使用索引,另外一個使用全表掃描。這時最好的解決辦法就是做一次ANALYZE TABLE的操作。

>> MySQL 5.5版本之前(不包括5.5)存在的一個普遍被人詬病的問題是MySQL資料庫對於索引的新增或者刪除的這類DDL操作,MySQL資料庫的操作過程為:

>> InnoDB儲存引擎從InnoDB 1.0.x版本開始支援一種稱為Fast Index Creation(快速索引建立)的索引建立方式——簡稱FIC。

>> 對於輔助索引的建立,InnoDB儲存引擎會對建立索引的表加上一個S鎖。在建立的過程中,不需要重建表,因此速度較之前提高很多,並且資料庫的可用性也得到了提高。刪除輔助索引操作就更簡單了,InnoDB儲存引擎只需更新內部檢視,並將輔助索引的空間標記為可用,同時刪除MySQL資料庫內部檢視上對該表的索引定義即可。這裡需要特別注意的是,臨時表的建立路徑是通過引數tmpdir進行設定的。使用者必須保證tmpdir有足夠的空間可以存放臨時表,否則會導致建立索引失敗。

>> 由於FIC在索引的建立的過程中對錶加上了S鎖,因此在建立的過程中只能對該表進行讀操作,若有大量的事務需要對目標表進行寫操作,那麼資料庫的服務同樣不可用

>> Facebook採用PHP指令碼來現實OSC,而並不是通過修改InnoDB儲存引擎原始碼的方式。OSC最初由Facebook的員工Vamsi Ponnekanti開發。此外,OSC借鑑了開源社群之前的工具The openarkkit toolkit oak-online-alter-table。實現OSC步驟如下:

>> MySQL 5.6版本開始支援Online DDL(線上資料定義)操作,其允許輔助索引建立的同時,還允許其他諸如INSERT、UPDATE、DELETE這類DML操作,這極大地提高了MySQL資料庫在生產環境中的可用性。

>> LOCK部分為索引建立或刪除時對錶新增鎖的情況,可有的選擇為:

>> InnoDB儲存引擎實現Online DDL的原理是在執行建立或者刪除操作的同時,將INSERT、UPDATE、DELETE這類DML操作日誌寫入到一個快取中。待完成索引建立後再將重做應用到表上,以此達到資料的一致性。這個快取的大小由引數innodb_online_alter_log_max_size控制,預設的大小為128MB。若使用者更新的表比較大,並且在建立過程中伴有大量的寫事務,如遇到innodb_online_alter_log_max_size的空間不能存放日誌時,會丟擲類似如下的錯誤:

>> 對於這個錯誤,使用者可以調大引數innodb_online_alter_log_max_size,以此獲得更大的日誌快取空間。此外,還可以設定ALTER TABLE的模式為SHARE,這樣在執行過程中不會有寫事務發生,因此不需要進行DML日誌的記錄。

>> 如果某個欄位的取值範圍很廣,幾乎沒有重複,即屬於高選擇性,則此時使用B+樹索引是最適合的。例如,對於姓名欄位,基本上在一個應用中不允許重名的出現。

>> 在InnoDB儲存引擎中,Cardinality統計資訊的更新發生在兩個操作中:INSERT和UPDATE。

>> 聯合索引的第二個好處是已經對第二個鍵值進行了排序處理。例如,在很多情況下應用程式都需要查詢某個使用者的購物情況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引可以避免多一次的排序操作,因為索引本身在葉子節點已經排序了。

>> 正如前面所介紹的那樣,聯合索引(a,b)其實是根據列a、b進行排序,因此下列語句可以直接使用聯合索引得到結果:    SELECT ... FROM TABLE WHERE a=xxx ORDER BY b

>> InnoDB儲存引擎支援覆蓋索引(covering index,或稱索引覆蓋),即從輔助索引中就可以得到查詢的記錄,而不需要查詢聚集索引中的記錄。使用覆蓋索引的一個好處是輔助索引不包含整行記錄的所有資訊,故其大小要遠小於聚集索引,因此可以減少大量的IO操作。

>> 覆蓋索引的另一個好處是對某些統計問題而言的

>> 表buy_log有(userid,buy_date)的聯合索引,這裡只根據列b進行條件查詢,一般情況下是不能進行該聯合索引的,但是這句SQL查詢是統計操作,並且可以利用到覆蓋索引的資訊,因此優化器會選擇該聯合索引

>> 這是為什麼呢?原因在於使用者要選取的資料是整行資訊,而OrderID索引不能覆蓋到我們要查詢的資訊,因此在對OrderID索引查詢到指定資料後,還需要一次書籤訪問來查詢整行資料的資訊。雖然OrderID索引中資料是順序存放的,但是再一次進行書籤查詢的資料則是無序的,因此變為了磁碟上的離散讀操作。如果要求訪問的資料量很小,則優化器還是會選擇輔助索引,但是當訪問的資料佔整個表中資料的蠻大一部分時(一般是20%左右),優化器會選擇通過聚集索引來查詢資料。因為之前已經提到過,順序讀要遠遠快於離散讀。

>> 因此對於不能進行索引覆蓋的情況,優化器選擇輔助索引的情況是,通過輔助索引查詢的資料是少量的。這是由當前傳統機械硬碟的特性所決定的,即利用順序讀來替換隨機讀的查詢。若使用者使用的磁碟是固態硬碟,隨機讀操作非常快,同時有足夠的自信來確認使用輔助索引可以帶來更好的效能,那麼可以使用關鍵字FORCE INDEX來強制使用某個索引

>> 因此,USE INDEX只是告訴優化器可以選擇該索引,實際上優化器還是會再根據自己的判斷進行選擇。而如果使用FORCE INDEX的索引提示,如:

>> MySQL5.6版本開始支援Multi-Range Read(MRR)優化。Multi-Range Read優化的目的就是為了減少磁碟的隨機訪問,並且將隨機訪問轉化為較為順序的資料訪問,這對於IO-bound型別的SQL查詢語句可帶來效能極大的提升。

>> MRR的工作方式如下:□ 將查詢得到的輔助索引鍵值存放於一個快取中,這時快取中的資料是根據輔助索引鍵值排序的。□ 將快取中的鍵值根據RowID進行排序。□ 根據RowID的排序順序來訪問實際的資料檔案。

>> 之前的MySQL資料庫版本不支援Index Condition Pushdown,當進行索引查詢時,首先根據索引來查詢記錄,然後再根據WHERE條件來過濾記錄。在支援Index Condition Pushdown後,MySQL資料庫會在取出索引的同時,判斷是否可以進行WHERE條件的過濾,也就是將WHERE的部分過濾操作放在了儲存引擎層

>> 當優化器選擇Index Condition Pushdown優化時,可在執行計劃的列Extra看到Using index condition提示。

>> 自適應雜湊索引採用之前討論的雜湊表的方式實現。不同的是,這僅是資料庫自身建立並使用的,DBA本身並不能對其進行干預。自適應雜湊索引經雜湊函式對映到一個雜湊表中,因此對於字典型別的查詢非常快速

>> 全文檢索通常使用倒排索引(inverted index)來實現。倒排索引同B+樹索引一樣,也是一種索引結構。它在輔助表(auxiliary table)中儲存了單詞與單詞自身在一個或多個文件中所在位置之間的對映。這通常利用關聯陣列實現,其擁有兩種表現形式:

>> full inverted index還儲存了單詞所在的位置資訊,如code這個單詞出現在(1∶6),即文件1的第6個單詞為code。相比之下,full inverted index佔用更多的空間,但是能更好地定位資料,並擴充一些其他的搜尋特性。

>> InnoDB儲存引擎從1.2.x版本開始支援全文檢索的技術,其採用full inverted index的方式

>> 。在InnoDB儲存引擎中,將(DocumentId,Position)視為一個“ilist”。因此在全文檢索的表中,有兩個列,一個是word欄位,另一個是ilist欄位,並且在word欄位上有設有索引

>> 正如之前所說的那樣,倒排索引需要將word存放到一張表中,這個表稱為Auxiliary Table(輔助表)。在InnoDB儲存引擎中,為了提高全文檢索的並行效能,共有6張Auxiliary Table,目前每張表根據word的Latin編碼進行分割槽。

>> Auxiliary Table是持久的表,存放於磁碟上。然而在InnoDB儲存引擎的全文索引中,還有另外一個重要的概念FTS Index Cache(全文檢索索引快取),其用來提高全文檢索的效能。FTS Index Cache是一個紅黑樹結構,其根據(word,ilist)進行排序。這意味著插入的資料已經更新了對應的表,但是對全文索引的更新可能在分詞操作後還在FTS Index Cache中,Auxiliary Table可能還沒有更新。InnoDB儲存引擎會批量對Auxiliary Table進行更新,而不是每次插入後更新一次Auxiliary Table

>> FTS Document ID是另外一個重要的概念。在InnoDB儲存引擎中,為了支援全文檢索,必須有一個列與word進行對映,在InnoDB中這個列被命名為FTS_DOC_ID,其型別必須是BIGINT UNSIGNED NOT NULL,並且InnoDB儲存引擎自動會在該列上加入一個名為FTS_DOC_ID_INDEX的Unique Index

>> 文件中分詞的插入操作是在事務提交時完成,然而對於刪除操作,其在事務提交時,不刪除磁碟Auxiliary Table中的記錄,而只是刪除FTS Cache Index中的記錄

>> 由於文件的DML操作實際並不刪除索引中的資料,相反還會在對應的DELETED表中插入記錄,因此隨著應用程式的允許,索引會變得非常大,即使索引中的有些資料已經被刪除,查詢也不會選擇這類記錄。

>> 通過設定引數innodb_ft_aux_table來檢視分詞對應的資訊:

>> SELECT * FROM information_schema.INNODB_FT_INDEX_TABLE;

>> 當前InnoDB儲存引擎的全文檢索還存在以下的限制:□ 每張表只能有一個全文檢索的索引。□ 由多列組合而成的全文檢索的索引列必須使用相同的字符集與排序規則。□ 不支援沒有單詞界定符(delimiter)的語言,如中文、日語、韓語等。

第6章 鎖

>> 鎖是資料庫系統區別於檔案系統的一個關鍵特性。鎖機制用於管理對共享資源的併發訪[插圖]

>> 2005版本,Microsoft SQL Server開始支援樂觀併發和悲觀併發,在樂觀併發下開始支援行級鎖,但是其實現方式與InnoDB儲存引擎的實現方式完全不同。使用者會發現在Microsoft SQL Server下,鎖是一種稀有的資源,鎖越多開銷就越大,因此它會有鎖升級。在這種情況下,行鎖會升級到表鎖,這時併發的效能又回到了以前。

>> latch一般稱為閂鎖(輕量級的鎖),因為其要求鎖定的時間必須非常短。若持續的時間長,則應用的效能會非常差。在InnoDB儲存引擎中,latch又可以分為mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證併發執行緒操作臨界資源的正確性,並且通常沒有死鎖檢測的機制。

>> 對於InnoDB儲存引擎中的latch,可以通過命令SHOW ENGINE INNODB MUTEX來進行檢視

>> 相對於latch的檢視,lock資訊就顯得直觀多了。使用者可以通過命令SHOW ENGINE INNODB STATUS及information_schema架構下的表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS來觀察鎖的資訊

>> 如果一個事務T1已經獲得了行r的共享鎖,那麼另外的事務T2可以立即獲得行r的共享鎖,因為讀取並沒有改變行r的資料,稱這種情況為鎖相容(Lock Compatible)。但若有其他的事務T3想獲得行r的排他鎖,則其必須等待事務T1、T2釋放行r上的共享鎖——這種情況稱為鎖不相容。

>> S和X鎖都是行鎖,相容是指對同一記錄(row)鎖的相容性情況。

>> nnoDB儲存引擎支援意向鎖設計比較簡練,其意向鎖即為表級別的鎖。設計目的主要是為了在一個事務中揭示下一行將被請求的鎖型別。

>> 在InnoDB 1.0版本之前,使用者只能通過命令SHOW FULL PROCESSLIST,SHOW ENGINE INNODB STATUS等來檢視當前資料庫中鎖的請求,然後再判斷事務鎖的情況。從InnoDB1.0開始,在INFORMATION_SCHEMA架構下添加了表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS。通過這三張表,使用者可以更簡單地監控當前事務並分析可能存在的鎖問題

>> 在通過表INNODB_LOCKS查看了每張表上鎖的情況後,使用者就可以來判斷由此引發的等待情況了。當事務較小時,使用者就可以人為地、直觀地進行判斷了。但是當事務量非常大,其中鎖和等待也時常發生,這個時候就不這麼容易判斷。但是通過表INNODB_LOCK_WAITS,可以很直觀地反映當前事務的等待

>> 一致性的非鎖定讀(consistent nonlocking read)是指InnoDB儲存引擎通過行多版本控制(multi versioning)的方式來讀取當前執行時間資料庫中行的資料。如果讀取的行正在執行DELETE或UPDATE操作,這時讀取操作不會因此去等待行上鎖的釋放。相反地,InnoDB儲存引擎會去讀取行的一個快照資料。

>> 之所以稱其為非鎖定讀,因為不需要等待訪問的行上X鎖的釋放。快照資料是指該行的之前版本的資料,該實現是通過undo段來完成。而undo用來在事務中回滾資料,因此快照資料本身是沒有額外的開銷

>> 非鎖定讀機制極大地提高了資料庫的併發性。在InnoDB儲存引擎的預設設定下,這是預設的讀取方式,即讀取不會佔用和等待表上的鎖。但是在不同事務隔離級別下,讀取的方式不同,並不是在每個事務隔離級別下都是採用非鎖定的一致性讀。此外,即使都是使用非鎖定的一致性讀,但是對於快照資料的定義也各不相同。

>> 一個行記錄可能有不止一個快照資料,一般稱這種技術為行多版本技術。由此帶來的併發控制,稱之為多版本併發控制(Multi Version Concurrency Control,MVCC)。

>> 在事務隔離級別READ COMMITTED和REPEATABLE READ(InnoDB儲存引擎的預設事務隔離級別)下,InnoDB儲存引擎使用非鎖定的一致性讀。然而,對於快照資料的定義卻不相同。在READ COMMITTED事務隔離級別下,對於快照資料,非一致性讀總是讀取被鎖定行的最新一份快照資料。而在REPEATABLE READ事務隔離級別下,對於快照資料,非一致性讀總是讀取事務開始時的行資料版本

>> 需要特別注意的是,對於READ COMMITTED的事務隔離級別而言,從資料庫理論的角度來看,其違反了事務ACID中的I的特性,即隔離性

>> 但是在某些情況下,使用者需要顯式地對資料庫讀取操作進行加鎖以保證資料邏輯的一致性。而這要求資料庫支援加鎖語句,即使是對於SELECT的只讀操作。InnoDB儲存引擎對於SELECT語句支援兩種一致性的鎖定讀(locking read)操作:□ SELECT…FOR UPDATE□ SELECT…LOCK IN SHARE MODE

>> SELECT…FOR UPDATE對讀取的行記錄加一個X鎖,其他事務不能對已鎖定的行加上任何鎖。SELECT…LOCK IN SHARE MODE對讀取的行記錄加一個S鎖,其他事務可以向被鎖定的行加S鎖,但是如果加X鎖,則會被阻塞。

>> 對於一致性非鎖定讀,即使讀取的行已被執行了SELECT…FOR UPDATE,也是可以進行讀取的,這和之前討論的情況一樣。此外,SELECT…FOR UPDATE,SELECT…LOCK IN SHARE MODE必須在一個事務中,當事務提交了,鎖也就釋放了。

>> 插入操作會依據這個自增長的計數器值加1賦予自增長列。這個實現方式稱做AUTO-INC Locking。這種鎖其實是採用一種特殊的表鎖機制,為了提高插入的效能,鎖不是在一個事務完成後才釋放,而是在完成對自增長值插入的SQL語句後立即釋放

>> 雖然AUTO-INC Locking從一定程度上提高了併發插入的效率,但還是存在一些效能上的問題。首先,對於有自增長值的列的併發插入效能較差,事務必須等待前一個插入的完成(雖然不用等待事務的完成)。其次,對於INSERT…SELECT的大資料量的插入會影響插入的效能,因為另一個事務中的插入會被阻塞。

>> 從MySQL 5.1.22版本開始,InnoDB儲存引擎中提供了一種輕量級互斥量的自增長實現機制,這種機制大大提高了自增長值插入的效能。

>> 還需要特別注意的是InnoDB儲存引擎中自增長的實現和MyISAM不同,MyISAM儲存引擎是表鎖設計,自增長不用考慮併發插入的問題。因此在master上用InnoDB儲存引擎,在slave上用MyISAM儲存引擎的replication架構下,使用者必須考慮這種情況。

>> InnoDB儲存引擎有3種行鎖的演算法,其分別是:□ Record Lock:單個行記錄上的鎖□ Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄本身□ Next-Key Lock∶Gap Lock+Record Lock,鎖定一個範圍,並且鎖定記錄本身

>> 當查詢的索引含有唯一屬性時,InnoDB儲存引擎會對Next-Key Lock進行優化,將其降級為Record Lock,即僅鎖住索引本身,而不是範圍。

>> InnoDB儲存引擎預設的事務隔離級別是REPEATABLE READ,在該隔離級別下,其採用Next-Key Locking的方式來加鎖。而在事務隔離級別READ COMMITTED下,其僅採用Record Lock,因此在上述的示例中,會話A需要將事務的隔離級別設定為READ COMMITTED