1. 程式人生 > >MySQL筆記(7)---事務

MySQL筆記(7)---事務

3.1 面具 repeat 打開 性能 mas nbsp number 疑惑

1.前言

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

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

  InnoDB的事務符合ACID的特性:

    原子性 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)

  問題在於如果故障了就要重新開始,這個代價太大了,回滾操作就有可能耗時巨大。因此對於長事務的處理,可以轉化為小批量的事務來進行處理。當事務發送錯誤時,只需要回滾一部分數據,然後接著上次已完成的事務繼續執行。

MySQL筆記(7)---事務