MySQL筆記(7)---事務

1.前言

  前面具體講了MySQL中的鎖實現的方式,解釋了是如何保證數據在併發狀況下的可靠性,並提到了事務REPETABLE READ和READ COMMITTED,解釋了一下這兩種事務的不一樣。本章講具體就事務的實現過程進行記錄,掃除這塊讓人疑惑的知識點。mysql

  事務是數據庫區別於文件系統的一個重要特性之一。文件系統中,若是寫文件時系統崩潰了,可能文件就被損壞了。雖然有機制將一個文件回退到某個時間點,但對於文件同步問題就無能爲力了。事務會把數據從一種一致性狀態轉換成另外一種一致性狀態。數據庫提交時,要不都成功了,要不都失敗了。算法

  InnoDB的事務符合ACID的特性:sql

    原子性  atomicity數據庫

    一致性 consistency數組

    隔離性 isolation緩存

    持久性 durability服務器

  前一章的鎖主要是對事務的隔離性進行了說明,本節主要關注事務的原子性這個概念。網絡

2.認識事務

2.1 概述

  事務是訪問並更新數據庫中數據的一個程序執行單元,能夠由1到多條SQL組成。在一個事務中,要不全作,要不不作。能夠把事務看作是辦一件事,有不少個步驟,只要事情搞砸了,全部的步驟都不被承認,比如沒幹。數據結構

  事務有很嚴格的定義,必須知足ACID特性,可是因爲各類緣由,有些時候並無知足全部的標準,好比以前提到的READ COMMITTED,其就不知足隔離性要求。大部分狀況,這樣不會形成嚴重的後果,反而會有性能的提高,因此具體使用哪一種事務,給了人們自由選擇的能力。架構

  原子性:就是要不不作,要不全作。好比轉帳,先扣本身帳戶的錢,再往其餘人帳戶上加一筆錢。這個過程是要保證原子性的,否則扣成功了,卻轉失敗了,後果是災難性的。原子就意味着不可分割,失敗了就要還原到最初的狀態。

  一致性:一致性指事務將數據庫從一種狀態轉變成下一種一致的狀態。在事務開始以前,結束以後,數據庫的完整性約束沒有被破壞。好比一個字段是惟一索引,對其進行了修改,事務提交或事務操做後變得非惟一了,這就破壞了一致性要求。

  隔離性:要求每一個讀寫事務的對象對其餘事務的操做能相互分離,即每一個事務當前看不到其餘事務的操做。

  持久性:事務一旦提交,結果就是永久性的。磁盤損壞之類的不是該考慮範圍。

2.2 分類

  從事務理論的角度來講,能夠把事務分爲如下幾種類型:

    扁平事務(Flat Transactions)

    帶有保存點的扁平事務(Flat Transactions with Savepoints)

    鏈事務(Chained Transactions)

    嵌套事務(Nested Transactions)

    分佈式事務(Distributed Transactions)

  扁平事務:

    最簡單,使用頻繁。全部操做都是處於同一層次,由BEGIN WORK開始,由COMMIT WORK 或ROLLBACK WORK結束,操做是原子的。成功完成的佔96%,應用程序要求中止的3%,強制終止的爲1%。這個是結果的佔用百分比。

    扁平事務的主要限制就是不能部分回滾。好比定機票:定了xx->yy->zz的機票,yy->zz的機票預約失敗,連xx->yy的都會回滾,顯然有些時候這並非咱們但願的。

  帶有保存點的扁平事務:

    這個就是解決上面提到的問題,給了一個保存點,後續失敗後能夠回到這個保存點。保存點使用SAVE WORK函數建立,出現問題,可以做爲內部的重啓動點,根據應用邏輯能夠選擇回到哪個保存點。

    ROLLBACK WORK : 保存點。

  鏈事務:

    能夠當作是保存點模式的一種變種。前一種在系統崩潰時,全部保存點都會消失,由於其非持久,事務恢復時要從新執行。

    鏈事務的思想是:提交一個事務時,釋放不須要的數據對象,將必要的處理上下文隱式地傳給下一個要開始地事務。提交事務和下一個事務開始操做是一個原子操做。這就意味着下一個事務將看到上一個事務地結果,在那個事務中執行同樣。

    經過上述描述,能夠看出這個與帶有保存點的扁平事務不一樣之處在於:只能回滾到上一個節點,由於以前的已經commit了。第二個就是因爲以前commit了,因此以前的鎖被釋放了。

  嵌套事務:

    這是一個層次結構框架。由一個頂層事務控制各個層次的事務。下面嵌套的事務被稱爲子事務,控制每個局部的變換。

    嵌套事務是一個樹,子樹能夠是嵌套事務,也能夠是扁平事務。葉子節點是扁平事務,根節點的是頂層事務,其餘的是子事務,前驅是父事務,後繼是兒子事務。子事務能夠提交和回滾,可是不會馬上生效,除非其父事務已經提交了。全部,全部的子事務都在頂層事務提交以後才真正提交。任意事務回滾都會致使其子事務回滾,因此子事務只有ACI特性,沒有D特性。

    在Moss理論中,實際的工做是交給葉子節點完成的,高層事務負責邏輯控制,決定什麼時候調用相關子事務。即便一個系統不支持嵌套事務,也能夠經過保存點來模擬嵌套事務。可是用戶沒法選擇哪些鎖被子事務繼承,哪些須要被父事務保留。保存點中全部的鎖都可以被訪問和獲得。可是嵌套事務中不同,好比父事務P1,持有對象X和Y的排他鎖,調用一個子事務P11,父事務能夠決定傳遞哪些鎖,若是P11還有z鎖,父事務能夠反向繼承,持有X、Y、Z的排他鎖。調用子事務P12,能夠選擇持有哪些鎖。

    想要實現事務的並行性,就須要真正支持嵌套事務了,保存點是不行的。

  分佈式事務:

    一般是一個在分佈式環境下運行的扁平事務,所以須要根據數據所在位置訪問網絡中的不一樣節點。好比一個事務,涉及到多個數據庫的增刪,其中有一個出錯,全部的數據庫實例都要作出相應的反應才行。

  InnoDB引擎支持扁平事務,帶有保存點的事務,鏈事務和分佈式事務。不支持嵌套事務,可是能夠試着用帶有保存點的事務進行模擬串行的嵌套事務。

3. 事務的實現

  以前提到了redo頁和undo頁,這裏詳細講一下這些知識。

  redo log稱爲重作日誌,用來保證事務的原子性和持久性。undo log用來保證事務的一致性。

  或許會認爲undo是redo的逆過程,可是不是。redo和undo的做用都是一種恢復操做,redo恢復提交事務修改的頁操做,undo是回滾記錄到某個特定版本。所以二者記錄的內容不一樣,redo是物理日誌,記錄的是頁的物理修改操做。undo是邏輯日誌,記錄每行的記錄。

3.1 redo

3.1.1 基本概念

  重作日誌用於實現事務的持久性,由兩部分組成:一是內存中的重作日誌緩衝,二是重作日誌文件。

  採起Force Log at commit機制實現事務的持久性。即當事務提交時,必須先將事務的日誌寫入重作日誌文件進行持久化,以後才進行commit,最後纔算完成。重作日誌在InnoDB中由兩部分組成:redo log和undo log。redo log保證事務的持久性,undo log幫助事務回滾及MVCC的功能。redo log基本是順序寫的,運行時不須要對redo log文件進行讀取操做。undo log是須要隨機讀寫的。

  重作日誌打開並無使用O_DIRECT選項,因此會先寫入文件系統緩存,要確保持久化了,必須調用fsync操做,這個操做的性能取決於磁盤,因此事務的性能由磁盤決定。

  InnoDB容許用戶不強制事務調用fsync操做,而是由最初提到的線程在一個週期中執行fsync操做。這個會提升性能,同時帶來了宕機時丟失事務的風險。

  innodb_flush_log_at_trx_commit用來控制重作日誌刷新到磁盤的策略。默認爲1,表示提交事務就執行一次fsync操做。0表示事務提交時不進行重作日誌操做,這個操做在master thread中完成,其每秒進行一次fsync操做。2表示事務提交時將重作日誌寫入文件,但只寫入文件系統緩存,不進行fsync操做。宕機時會丟失未從文件系統緩存刷入磁盤的那部分數據。50萬行數據在0模式可能須要13秒,1模式2分鐘,2模式23秒。因此0和2模式能夠提升性能,可是事務的ACID特性就沒法保證。

  以前說過MySQL中的二進制日誌文件,其用來進行POINT-IN-TIME的恢復和主從複製環境的創建。表面上和重作日誌類似,都是記錄了數據庫的操做日誌,可是二者有很大的不一樣。重作日誌是InnoDB產生的,二進制日誌是MySQL上層產生的。任何存儲引擎的更改都會產生二進制日誌。其次,兩種日誌的記錄內容不一樣,二進制是一種邏輯日誌,記錄的是對應的SQL語句,InnoDB的重作日誌是物理格式日誌,記錄的是對每一個頁的修改。此外,二進制日誌是在事務提交完成後一次寫入,重作日誌是在進行中不斷被寫入。

3.1.2 log block

  InnoDB中重作日誌是512字節進行存儲的,這意味着重作日誌都是以塊的方式進行保存的,稱之爲重作日誌塊,每塊大小512字節。若一個頁中產生的重作日誌大於512字節,那麼須要分割成多個重作日誌塊進行存儲,由於是512字節,因此日誌的寫入能夠保證原子性,不須要doublewrite技術。

  重作日誌包含日誌塊頭及塊尾,固然還有日誌自己。日誌頭一共12字節,尾8個字節,實際可存儲大小是492字節。

  log block header:

    LOG_BLOCK_HDR_NO    4字節

    LOG_BLOCK_HDR_DATA_LEN  2字節

    LOG_BLOCK_FIRST_REC_GROUP  2字節

    LOG_BLOCK_CHECKPOINT_NO   4字節

  log buffer好似一個數組,NO就是用來標記數組的位置,遞增其循環使用。第一位用來判斷flush bit,因此最大值是2^31。

  LEN代表log block所佔用的大小,最大就是0x200,512字節佔滿了。

  REC_GROUP表示log block中第一個日誌的偏移量,若是和LEN相同,表面當前log block不包含新的日誌。好比第一個事務使用了500字節,超過了492的最大值,而第二個事務使用了100字節,因此就會有兩個block,1事務的尾部和2事務的數據在一個block中。因此第二個block的偏移量爲8+12。

  CHECKPOINT_NO表示最後寫入時的檢查點4字節的值。

  log block tailer就一個部分,和LOG_BLOCK_HDR_NO相同。

3.1.3 log group

  log group是重作日誌組,有多個重作日誌文件。InnoDB源碼中已支持log group鏡像功能,可是在ha_innobase.cc文件中禁止了該功能。所以InnoDB存儲引擎只有一個log group。

  log group是一個概念,沒有實際存儲的物理文件。由多個重作日誌文件組成,每一個日誌文件大小是相同的,在InnoDB 1.2以前,重作日誌大小小於4GB,1.2版本提高了大小的限制爲512GB。1.1版本就支持大於4GB的重作日誌。

  存儲引擎運行過程當中,log buffer是按必定的規則將內存中的log block刷新到磁盤:

    事務提交時,log buffer的一半空間被使用,log checkpoint時。

  對於log block的寫入追加在redo log file的最後部分,當一個redo log file被寫滿時,會寫入下一個redo log file,使用的方式是round-robin。

  雖然log block老是追加在redo log file的最後,可是寫入並不都是順序的,由於除了保存了log block,還保存了一些其餘信息,一共佔2kb大小,即每一個redo log file的前2kb都不保存log block的信息。對於log group的第一個redo log file,其前2kb的部分保存了4個512字節大小的塊。

    log file header    512

    checkpoint1     512

    空        512

    checkpoint2     512

  上述信息只在每一個log group的第一個file中保存。其他file 保留空間,但不保存信息。因爲這些信息,致使寫入不是順序的,由於須要更新這2KB的數據,這些信息對於InnoDB的恢復操做十分重要。後面的checkpoint的值,設計上是交替寫入,這樣設計避免了因介質失敗致使沒法找到可用的checkpoint的狀況。

3.1.4 重作日誌格式

  不一樣的數據庫有對應的重作日誌格式。此外,InnoDB存儲是基於頁的,因此重作日誌格式也是基於頁的。雖然有不一樣的重作日誌格式,可是其有着通用的頭部格式。

    redo_log_type:重作日誌的類型

    space: 表空間的ID

    page_no:頁的偏移量

    redo_log_body:根據重作日誌類型不一樣,會有不一樣的存儲內容,InnoDB1.2版本時,有51種重作日誌格式,以後會愈來愈多。插入和刪除的格式就不同。

3.1.5 LSN

  這個是Log Sequence Number的縮寫,表明的是日誌的序列號。佔8個字節,單調遞增。含義是:

    重作日誌的寫入字節總量

    checkpoint的位置

    頁的版本

  LSN不只記錄在重作日誌中,每一個頁中的頭部有一個值FIL_PAGE_LSN記錄了該頁的LSN。在頁中表示頁最後操做的LSN的大小。由於重作日誌是每一個頁的日誌,因此能夠經過LSN判斷一個頁是否須要進行恢復操做。如,P1頁LSN是10000,數據庫啓動時檢測到重作日誌的LSN爲13000,而且事務提交了,那麼就須要進行恢復操做,將重作日誌應用到P1頁中。對於LSN小於P1頁的LSN不須要進行重作,由於P1的LSN代表其已經操做完這些內容了。

3.1.6 恢復

  數據庫啓動時會嘗試進行恢復。由於重作日誌記錄的是物理日誌,因此恢復速度比邏輯日誌要快。InnoDB也對恢復進行了必定程度的優化,好比順序讀取並行應用重作日誌,這樣能夠進一步提升數據庫的恢復速度。

  因爲checkpoint表示已經刷新到磁盤頁上的LSN,因此恢復只須要從checkpoint開始的日誌部分。

3.2 undo

3.2.1 基本概念

  重作日誌記錄了事務的行爲,能夠很好的經過其對頁進行重作操做。可是事務有時候須要回滾,這時就須要undo。因此對數據庫修改時,InnoDB存儲引擎還會產生undo,回滾時就能夠恢復原來的樣子。

  redo存放在重作日誌文件中,undo放在數據庫內部的一個特殊段中,這個就是undo段,位於共享表空間內。

  undo並非將數據庫物理地恢復到執行語句或事務以前的樣子,其是邏輯日誌,是經過邏輯恢復的方式。全部修改被邏輯的取消了,可是數據結構和頁自己在回滾後可能不大相同。這是由於在多用戶併發系統中,可能有不少個事務對數據記錄併發訪問,一個事務在修改一個頁中的幾條數據,另外一個事務在修改另外幾個數據,不能將一個頁回滾到事務開始的樣子,以前提到過B+樹修改可能會分裂,因此其中一個事務失敗,回滾到以前的樣子會影響到其餘事務的執行。

  undo的另外一個做用就是MVCC,若是用戶讀取一行記錄,被其餘事務佔用了,能夠經過undo讀取以前的行版本信息,實現非鎖定讀取。

  最後undo是邏輯日誌,操做也會產生redo log,這是由於undo log也須要持久性的保護。

3.2.2 undo存儲管理

  InnoDB存儲引擎對undo採起段的方式管理,首先有rollback segment,每一個回滾段中記錄了1024個undo log segment,而在每一個undo log segment段中進行undo頁申請。共享表空間偏移量爲5的頁(0,5)記錄了全部rollback segment header所在的頁,這個頁的類型爲FIL_PAGE_TYPE_SYS。

  1.1版本以前,只有一個rollback segment,因此支持的在線事務限制是1024,從1.1版本開始支持128個rollback segment,提升到了128*1024。這些數據依舊存儲在共享表空間中。

  1.2版本開始能夠設置:

    innodb_undo_directory: rollback segment文件所在的路徑,意味着能夠放在共享表之外的位置,默認"."表示當前InnoDB存儲引擎的目錄

    innodb_undo_logs: 設置rollback segment的個數,默認128,替換了以前的innodb_rollback_segments參數

    innodb_undo_tablespaces: 設置構成rollback segment文件的數量,這樣rollback segment能夠較平均地分佈在多個文件中。能夠看見undo前綴的文件,這個就是rollback segment文件了。

  注意,undo log segment分配頁並寫入undo log這個過程也會寫入重作日誌,事務提交時,InnoDB會進行下面兩件事:

    將undo log放入列表中,供以後的purge操做

    判斷undo頁是否能夠重用,若能夠分配給下個事務使用

  事務提交後不會馬上刪除undo log,由於其餘事務須要經過undo log獲得行記錄以前的版本,因此事務提交時將undo放入一個鏈表,是否能夠刪除undo log及其頁由Purge線程判斷。

  此外,分配undo頁會浪費空間,因此須要重用。具體來講,事務提交時,將undo log放入鏈表,而後判斷使用空間是否小於3/4,若小於表面能夠被重用,以後的undo log記錄在當前undo log的後面,因爲存放undo log的記錄的列表是以記錄進行組織的,undo頁可能存放不一樣事務的undo log,因此purge操做須要涉及磁盤的離散讀取操做,是一個比較慢的過程。

3.2.3 undo log的格式

  undo log分爲:insert undo log和update undo log。

  insert對於其餘事務不可見,因此使用完後能夠直接刪除,不須要進行purge操做。

  udpate對應delete和update操做,可能須要提升MVCC機制,因此不能直接刪除,放入undo log鏈表中,等待purge肯定。

  這兩種日誌結構比較複雜,不進行敘述。

3.2.4 查看undo信息

  information_schema下面有兩張數據字典表:

    INNODB_TRX_ROLLBACK_SEGMENT:查看rollback segment信息

    INNODB_TRX_UNDO:記錄事務對應的undo log。

  delete操做並非直接刪除記錄,而只是將記錄標記爲已刪除,記錄的最終刪除是在purge操做完成的。

  update操做是分兩步實現的,首先將原主鍵記錄標記爲刪除,須要產生一條TRX_UNDO_DEL_MARK_REC的undo log,以後插入一條新的記錄,產生一條TRX_UNDO_INSERT_REC的undo log。

3.3 purge操做

  上面提到delete和update操做並無直接刪除數據,這些操做都是將頁標記成刪除狀態,最終刪除操做是purge操做中完成的。由於InnoDB支持MVCC,因此記錄不能在事務提交時當即進行處理,這時其餘事務可能正在引用這行。若是這條記錄不被任何事務引用,就能夠進行真正的刪除操做。

  前面說過爲了節省空間,undo的頁被設計成:一個頁上容許有多個事務的undo log存在,雖然不表明事務在全局過程當中的提交順序,可是後面的事務產生的undo log總在最後。此外,InnoDB還有一個history列表,它根據事務提交的順序,將undo log進行連接。

  在執行purge操做過程當中,會從history list中找到第一個須要被清理的記錄,trx1,清理以後會找其關聯的undo log所在的頁中是否存在能夠被清理的記錄,進行清理。找完後會繼續找下一個須要被清理的記錄,繼續清理undo頁,查找該頁上能夠被清理的其餘記錄。

  總的來講就是先從history list找到undo log,再從undo page找到undo log,這樣作的好處就在於避免大量隨機讀取操做,提升purge的效率。

  全局動態參數innodb_purge_batch_size用來設置每次purge操做須要清理的undo page數量。InnoDB 1.2以前的默認值是20,以後默認是300。通常而言,設置的越大回收的undo page也就越多,這樣可供重用的undo page就越多,減小了磁盤存儲空間與分配的開銷。不過,也不能設置的過大,會致使CPU和磁盤IO過於集中於對undo log的處理,性能降低。

  另外一方面,若是壓力很大時,不能有效的進行purge操做。那麼history list的長度會愈來愈長。全局動態參數innodb_max_purge_lag用來控制history list的長度,若長度大於這個參數時,會延遲DML操做。默認值是0,不作限制,大於0時計算方法是:

    (history_list.length - innodb_max_purge_lag) * 10 - 5 = delay,單位毫秒

  延緩的對象是行,不是一個DML操做。好比一個update涉及5行,總延遲時間就是5*delay。

  InnoDB1.2有一個新的動態參數innodb_max_purge_lag_delay,來控制這個delay的最大值,避免緩慢致使其餘SQL線程出現無限制的等待。

3.4 group commit

  事務非只讀操做時,每次提交時會進行一次fsync操做,保證日誌寫入磁盤。爲了提升fsync效率,就提供了group commit的功能,即一次fsync能夠刷新確保多個事務日誌被寫入文件。

  對於InnoDB而言,事務提交會進行兩個階段的操做:

    修改內存中事務對應的信息,並將日誌寫入重作日誌緩衝

    調用fsync將確保日誌都從重作日誌緩衝寫入磁盤。

  第2個步驟比1步驟慢,當執行2步驟時,其餘事務能夠進行1步驟,再進行2步驟,能夠將多個事務的重作日誌經過一次fsync刷新到磁盤,這樣就大大減少了磁盤的壓力。

  在InnoDB1.2以前,開始了二級制日誌後,group commit功能會失效,而且在線環境多使用replication環境,所以二進制日誌的選項基本爲開啓狀態,這個問題尤其顯著。致使這個問題的緣由在於:爲了保證存儲引擎層中的事務和二級制日誌的一致性,兩者之間使用了兩段事務:

    1.當事務提交時InnoDB進行prepare操做

    2.MySQL數據庫上層寫入二進制日誌

    3.InnoDB存儲引擎將日誌寫入重作日誌文件

      a.修改內存中事務對應的信息,而且將日誌寫入重作日誌緩衝。

      b.調用fsync將確保日誌都從重作日誌緩衝寫入磁盤。

  一旦步驟2完成,就確保了事務的提交,即便在執行步驟3時數據庫發生了宕機。每一個步驟須要執行一次fsync操做才能保證上下兩層數據的一致性。步驟2的fsync由參數sync_binlog控制,步驟3的fsync由參數innodb_flush_log_at_trx_commit控制。

  問題就出在順序上面,爲了保證二級制寫入順序和InnoDB的事務提交順序,會使用prepare_commit_mutex鎖,第三步的寫入重作日誌緩衝操做就不能在其餘事務執行fsync操做時進行了,破壞了group commit。二級制順序必須保證,由於備份和恢復時須要順序的。

  這個問題在2010年MySQL數據庫打會中提出,後面有了解決方案,MySQL 5.6採起了相似的方式實現,稱爲Binary Log Group Commit(BLGC)。

  數據庫上層進行提交時,先按順序放入隊列中,隊列的第一個事務爲leader,其餘的是follower。BLGC的步驟以下:

    1.flush階段,將每一個事務的二級制日誌寫入內存中

    2.Sync階段,將內存的二進制日誌刷新到磁盤,多個就只須要一次fsync就能完成,這就是BLGC

    3.commit階段,leader按照順序調用存儲引擎層事務的提交,InnoDB本就支持group commit,就沒問題了。

  當一組事務進行Commit時,其餘的能夠進行flush,使得group commit生效,group commit的效果和隊列中事務的數量相關,數量越多,效果越好。

  參數binlog_max_flush_queue_time用來控制flush階段中等待的時間,即便以前的一組事務完成提交,當前的也不會立刻進入sync,等待一段時間。好處就是group commit的事務就更多了,這可能會致使事務響應時間變慢。默認爲0,推薦使用這個值。除非有100個鏈接,而且在不斷的寫入或更新操做。

4. 事務控制語句

  MySQL命令行下事務是默認提交的。須要顯示的開啓一個事務BEGIN、START TRANSACTION或者執行命令SET AUTOCOMMIT=0。

  事務控制語句:

    START  TRANSACTION | BEGIN:顯示地開啓一個事務

    COMMIT: 提交事務,修改爲爲永久的,COMMIT WORK含義相似

    ROLLBACK:回滾會結束用戶的事務,並撤銷正在進行的全部未提交的修改, ROLLBACK WORK含義相似

    SAVEPOINT indentifier:SAVEPOINT容許在事務中建立一個保存點,能夠有多個保存點

    RELEASE SAVEPOINT identifier:刪除一個事務的保存點,沒有保存點時會拋出異常

    ROLLBACK TO [SAVEPOINT] identifier:這個語句和SAVEPOINT一塊兒使用。能夠把事務回滾到標記點,以前的工做保留。

    SET TRANSACTION: 設置事務的隔離級別。InnoDB中有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

  COMMIT WORK的不一樣之處在於用來控制事務結束後的行爲是CHAIN仍是RELEASE,若是是CHAIN就變成了鏈事務。能夠經過參數completion_type進行控制,默認是0,表示沒有任何操做,和COMMIT相同。爲1時就等同於COMMIT AND CHAIN,表示立刻自動開啓一個相同隔離級別的事務。爲2時等同於COMMIT AND RELEASE,在事務提交後會自動斷開與服務器的鏈接。

  InnoDB存儲引擎中的事務都是原子的,構成事務的語句都會提交或者回滾。延伸單個語句就是要麼所有成功,要麼徹底回滾,所以一條語句失敗並拋出異常時,不會致使先前執行的語句自動回滾,由用戶本身決定是否對其進行提交或回滾操做。

  另外一個容易犯錯的就是ROLLBACK TO SAVEPOINT,雖然有回滾,可是事務並無結束,須要顯示執行COMMIT或者ROLLBACK。

5. 隱式提交的SQL語句

  如下的SQL語句會產生一個隱式的提交操做,即執行完後會有隱式的COMMIT操做。

    DDL語句:ALTER DATABASE... UPGRADE DATA DIRECTORY NAME,

         ALTER EVENT、 ALTER PROCEDURE、ALTER TABLE、 ALTER VIEW、

         CREATE DATABASE、 CREATE EVENT、 CREATE INDEX、 CREATE PROCEDURE、

         CREATE TABLE、 CREATE TRIGGER、 CREATE VIEW、 DROP DATABASE、 DROP EVENT、

         DROP INDEX、DROP PROCEDURE、 DROP TABLE、 DROP TRIGGER、 DROP VIEW、RENAME TABLE、

            TRUNCATE TABLE

    修改MySQL架構操做: CREATE USER、 DROP USER、 GRANT、 RENAME USER、 REVOKE、 SET PASSWORD

    管理語句: ANALYZE TABLE、CACHE INDEX、 CHECK TABLE、 LOAD INDEX、 INTO CACHE、 OPTIMIZE TABLE、 REPAIR TABLE

6. 對於事務操做的統計

  因爲InnoDB是支持事務的,所以須要考慮每秒請求數QPS,同時要關注每秒事務的處理能力TPS。

  計算TPS的方法是(com_commit+com_rollback)/time。前提是事務必須是顯示提交的,存在隱式事務的提交和回滾(默認autocommit=1),不會計算到com_commit和com_rollback中。

  還有兩個參數handler_commit和handler_rollback用於事務的統計操做。

7. 事務的隔離級別

  大部分數據庫系統都沒有提供真正的隔離性,最初多是由於對這些問題的理解不到位。如今雖然清楚了,可是在正確性和性能之間作了妥協。ISO和ANIS SQL標準制定了4種事務隔離級別,可是不多有遵照這些標準的。標準的隔離級別以下:

    1.READ UNCOMMITTED:稱爲瀏覽訪問,僅僅針對事務而言的。

    2.READ COMMITTED:稱爲遊標穩定

    3.REPEATABLE READ:2.9999°,沒有幻讀的保護

    4.SERIALIZABLE:3°隔離,這個是默認的隔離級別

  INNODB默認的隔離級別是REPEATABLE READ,可是和標準不一樣的是,其經過Next-key lock鎖的算法,避免了幻讀的產生。

  隔離級別越低,事務請求的鎖越少或保持鎖的時間越短。這也就是爲何大部分數據庫的事務隔離級別是READ COMMITTED了。

  SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL

  {

    READ UNCOMMITTED

    | READ COMMITTED

    | REPEATABLE READ

    | SERIALIZABLE

  }

  也能夠經過配置文件,在MySQL啓動時就設置默認的隔離級別:

    [mysqld]

    transaction-isolation=READ-COMMITTED

  查看當前會話的事務隔離級別 select @@tx_isolation

  在SERIALIABLE的事務隔離級別,InnoDB會對每一個SELECT語句後自動加上LOCK IN SHARE MODE,這樣對一致性非鎖定度再也不支持,理論上是符合數據庫要求的,即事務是well-formed的,而且是two-phrased的。

  由於InnoDB默認的REPEATABLE READ就能達到3°的隔離,因此通常事務不須要使用SERIALIABLE的隔離級別,那個主要用於分佈式事務。

  對於READ COMMITTED事務隔離級別下,除了惟一性的約束檢查及外鍵約束的檢測須要gap lock,InnoDB不會使用gap lock鎖算法。可是,在MySQL5.1中,READ COMMITTED事務隔離級別默認只能工做在replication二進制日誌爲ROW的格式下,若是是STATEMENT,會出錯誤。在MySQL5.0版本之前,不支持ROW格式的二進制時,經過innodb_locks_unsafe_for_binlog設置爲1能夠在二進制日誌爲STATEMENT下使用READ COMMITTED的事務隔離級別,可是可能會形成主從不一致的現象。

    在READ COMMITTED隔離級別下,沒有使用gap lock鎖定,致使能夠插入數據。

    在STTATEMENT格式記錄的是master上產生的SQL語句,因此在master上是先刪後插入,可是STATMENT格式的記錄倒是先插後刪,邏輯順序致使了不一致。

  能夠經過REPEATABLE隔離級別避免這個問題,MySQL5.1版本以後支持了ROW格式,就避免了這個問題。

8 分佈式事務

8.1 MySQL數據庫分佈式事務

  InnoDB存儲引擎提供了對XA事務的支持,並經過XA事務來支持分佈式事務的實現。分佈式事務指的是容許多個獨立的事務資源參與到一個全局的事務中。在使用分佈式事務的時候,InnoDB必須設置成SERIALIZABLE。

  XA事務容許不一樣數據庫之間的分佈式事務,好比一臺服務是MySQL,一臺是Oracle等。只要參與在全局事務中的每一個節點都支持XA事務。

  XA事務由一個或多個資源管理器、一個事務管理器以及一個應用程序組成。

    資源管理器:提供訪問事務資源的方法。一般一個數據庫就是一個資源管理器

    事務管理器:協調參與全局事務中的各個事務,須要和參與全局事務的全部資源管理器進行通訊

    應用程序:定義事務的邊界,指定全局事務中的操做。

  在MySQL中,資源管理器就是MySQL數據庫,事務管理器爲鏈接MySQL服務器的客戶端。

  分佈式事務使用兩段式提交的方式:

    第一階段,全部參與全局事務的節點都開始準備,告訴事務管理器它們準備好提交了

    第二階段,事務管理器告訴資源管理器執行ROLLBACK仍是COMMIT。若是任何一個節點不能提交,全部節點被告知回滾。

  XA {start | begin} xid {Join | resume}

  XA END xid [SUSPEND [for migrate]]

  XA PREPARE xid

  XA COMMIT xid [ONE PHASE]

  XA ROLLBACK xid

  XA RECOVER

  當前JAVA的JTA能夠很好的支持MySQL的分佈式事務,須要使用分佈式事務應該認真參考其API。

8.2 內部XA事務

  以前討論的分佈式事務是外部事務,即資源管理器是MySQL數據庫自己。還有一種分佈式事務,其在存儲引擎與插件之間,又或者是存儲引擎和存儲引擎之間,稱爲內部XA事務。

  最多見的就是binlog與InnoDB之間的了,因爲複製的須要,大部分數據庫開啓了binlog功能。提交時,先寫二進制日誌,再寫重作日誌,這兩個操做必須是原子性的。因此使用了XA事務,在提交時,先作一個PREPARE操做,將事務的xid寫入,接着進行二級制日誌的寫入。若是在InnoDB存儲引擎提交前,MySQL宕機了,在重啓後會檢查準備的uxid事務是否提交,沒有就會在存儲引擎層再進行一次提交操做。

9.長事務

  顧名思義,就是執行時間較長的事務,好比銀行系統,每一個階段就要更新帳戶利息。對應的帳號數量很是大。

    update account set account_total = account_total + (1 + interest_rate)

  問題在於若是故障了就要從新開始,這個代價太大了,回滾操做就有可能耗時巨大。所以對於長事務的處理,能夠轉化爲小批量的事務來進行處理。當事務發送錯誤時,只須要回滾一部分數據,而後接着上次已完成的事務繼續執行。

相關文章
相關標籤/搜索