事務隔離性由鎖來實現。原子性、一致性、持久性經過數據庫的redo log和undo log來完成。redo log稱爲重作日誌,用來保證事務的原子性和持久性。undo log用來保證事務的一致性。java
redo和undo做用都是一種恢復操做。mysql
基本概念算法
重作日誌用來實現事務的持久性,由兩部分組成sql
redo log buffer
redo log file
InnoDB是事務的存儲引擎,其經過Force Log at Commit
機制來實現事務的持久性,即當事務提交時,必須將該事務的全部日誌寫入到重作日誌文件進行持久化,該事務的COMMIT操做纔算完成。這裏的重作日誌文件包括redo 和 undo log。
用戶也能夠手工設置COMMIT日誌刷新策略,經過參數innodb_flush_log_at_trx_commit
來控制。數據庫
log block緩存
在INNODB中,重作日誌都是以512字節進行存儲的,由於大小和磁盤扇區大小同樣,所以重作日誌的寫入能夠保證原子性。服務器
log group併發
稱爲重作日誌組,其中有多個重作日誌文件,可是INNODB進行這個功能,實際上也只有一個log group。分佈式
是一個邏輯上的概念,由多個重作日誌文件組成,每一個log group中的日誌文件大小是相同的。大小最大爲512GB。ide
log buffer也是使用塊進行存儲的管理,一樣爲512字節。從緩存刷新到磁盤的具體規則爲:
重作日誌格式
INNODB的重作日誌格式是基於頁的。雖然有這邊冉的重作日誌格式,可是他們有着通用的頭部格式。
redo_log_type | space | page_no | redo log body |
---|---|---|---|
LSN
Log Sequence Number
縮寫,表示日誌序列號。在INNODB存儲引擎中,LSN佔用8字節,而且單調遞增,LSN表示含義有:
重作日誌寫入的總量
若當前重作日誌的LSN爲1000,一個事務寫入了100字節的重作日誌,那麼LSN變爲1100。
checkpoint的位置
頁的版本
每一個頁的頭部,有一個值FILE_PAGE_LSN
,記錄該頁的LSN。表示該頁最後刷新時LSN的大小。用於判斷頁是否須要進行恢復操做。例如P1的LSN爲10000,數據啓動時,檢測到重作日誌文件中的LSN爲13000,而且該事務已經提交,那麼數據庫就要對P1進行恢復操做。
恢復
無論運行時是否正常關閉,都會嚐盡進行恢復操做。由於記錄的都是物理日誌,因此恢復速度比邏輯日誌快不少。
基本概念
進行回滾操做時使用。undo存放在數據庫內部的一個特殊段中,成爲undo段。位於共享表空間中。undo是邏輯日誌,是將數據庫邏輯地恢復到原來的樣子。這是由於在多用戶併發系統中,可能有成百上千個併發事務,若是直接物理回滾頁記錄,會影響其餘正在進行的事務。
因此undo的回滾操做是邏輯操做,對於insert,進行對應的delete;對於delete,執行對象的Insert,對於update,進行反向的update。
undo的另一個做用是MVCC,實現了非鎖定讀取。
undo log也會產生redo log。這是由於undo log也須要持久性的保護。
undo存儲管理
採用段的方式進行管理,首先有rollback segment
,每一個回滾段中記錄了1024個undo log segment
,而在每一個undo log segment
段中進行undo頁的申請。
對rollback segment
作進一步的設置:
innodb_undo_directory
用於設置文件所在的路徑。便可以設置爲獨立表空間。該參數默認值爲「.」,表示當前INNODB存儲引擎的目錄。
innodb_undo_logs
用來設置rollback segment
個數,默認值爲128。
innodb_undo_tablespaces
設置構成rollback segment
文件的數量,這樣rollback segment
能夠較爲平均地分佈在多個文件中。
SHOW VARIABLES LIKE 'innodb_undo%'; SHOW VARIABLES LIKE 'datadir';
事務在undo log segment
分配頁並寫入undo log
的這個過程一樣須要寫入重作日誌。
undo log
放入列表中,以供以後的purge
操做undo log
所在的頁是否能夠重用,若能夠分配給下個事務使用。事務提交以後並不能立刻刪除undo log以及undo log所在的頁,這是由於可能有MVVC使用。因此將undo log放在一個鏈表中,是否能夠最終刪除由purge線程判斷。
undo log
格式
有兩種:
insert undo log
在insert操做中產生的undo log。因爲事務隔離性的要求,因此該undo log能夠在事務提交後直接刪除。不須要進行Purge操做。
update undo log
對delete和update操做產生的undo log
。改undo log
可能須要提供MVCC機制,所以不能在事務提交時就進行刪除。
查看undo 信息
# 查看rollback segment DESC INNODB_TRX_ROLLBACK_SEGMENT; # 查看rollback segment所在的頁 SELECT segment_id, space, page_no from INNODB_TRX_ROLLBACK_SEGMENT; # 記錄事務對應的undo log SELECT * FROM information_schema.INNODB_TRX_UNDO\G;
purge
delete和update操做可能並不直接刪除原有的數據,undo log只將對應記錄的delete flag設置爲1,沒有直接刪除記錄,真正刪除的操做被延時到purge操做中完成。
由於MVVC的關係,purge要等待該行記錄已經不被任何其餘事務引用,才進行清理操做。
INNODB存儲引擎中含有一個history list
,按照事務提交的順序將undo log
進行組織。在執行purge
的過程當中,INNODB存儲引擎首先從history list
中找到第一個須要被清理的記錄,清理以後,會繼續在該記錄所在的undo log頁中繼續尋找能夠被清除的記錄,而後再繼續從history list
中去查找,重複執行。
參數innodb_purge_batch_size
用來設置每次Purge操做須要清理的undo page
數量。
參數innodb_max_purge_lag
用來控制history list
的長度,若長度大於該參數時,其會「延緩」DML的操做。默認爲0,表示不作任何限制。當大於0時,就會延緩DML的操做,延緩的算法爲:
delay = ((length(history_list) - innodb_max_purge_lag) * 10) - 5
單位是毫秒。delay的對象是行,而不是DML操做。
參數innodb_max_purge_lag_delay
,用來控制delay的最大毫秒數。當上述計算的delay數值大於該參數值,限制住。
group commit
若事務爲非只讀事務,則每次提交都要進行依次fsync
操做,以此保證重作日誌都已經寫入磁盤。爲了提升磁盤fsync效率,提供了group commit
功能,依次fsync能夠刷新確保多個事務日誌被寫入文件。
可是在開啓了二進制日誌以後,爲保證存儲引擎層中的事務和二進制日誌的一致性,兩者之間使用了兩階段事務,步驟以下:
一旦寫入了二進制日誌,就確保了事務的提交,即便執行步驟3發生了宕機。此外,每一個步驟都須要進行依次fsync才能保證上下兩層數據一致。步驟二由sync_binlog
控制,步驟三由innodb_flush_log_at_trx_commit
控制。
由於備份以及恢復的須要,須要保證MYSQL數據庫上層二進制日誌的寫入順序和INNODB層的事務提交順序一致,MYSQL數據庫內部使用了prepare_commit_mutex
這個鎖。可是啓用這個鎖以後,步驟3中的步驟1不能夠再其餘事務執行步驟二時執行。從而致使group commit
失效。
爲了解決這個問題,5.6版本以後使用了BLGC
技術。
在MYSQL數據庫上層進行提交時先按照順序將其放入一個隊列中,隊列中的第一個事務成爲leader,其餘事務成爲follower,leader控制follower的行爲。BLGC的步驟分爲如下三個階段:
Flush
階段:將每一個事務的二進制日誌寫入內存。Sync
階段:將內存中的二進制日誌刷新到磁盤,若隊列中有多個事務,那麼僅一次fsync操做就完成了二進制日誌的寫入,這就是BLGC
。Commit
階段,leader根據順序調用存儲引擎層事務的提交,InnoDB存儲引擎本就支持Group commit
,因此就解決了gourp commit
失效的問題。 當一組事務在進行commit
階段時,其餘新事務能夠進行Flush
階段,從而使group commit
不斷生效。group commit
的效果由隊列中書屋的數量決定,若每次隊列中僅有一個事務,那麼可能效果和以前差很少,甚至會更差。但當提交的事務越多時,group commit的效果越明顯,數據庫性能的提高也就越大。
參數binlog_max_flush_queue_time
用來控制Flush
階段中等待的時間,即便以前的一組事務完成提交,當前一組的事務也不立刻進入Sync
階段,而是至少須要等待一段時間。這個好處是group commit
數量更多,該參數默認值爲0,推薦這是依舊爲0。
START TRANSACTION | BEGIN
:顯式地開啓一個事務COMMIT
:提交事務ROLLBACK
:回滾會結束用戶的事務,並撤銷正在進行的全部未提交的修改SAVEPOINT identifier
:容許在事務中建立一個保存點,一個事務中能夠有多個SAVEPOINT
。RELEASE SAVEPOINT identifier
:刪除一個事務的保存點,當沒有一個保存點執行這句語句時,會拋出一個異常。ROLLBACK TO [SAVEPOINT] identifier
:與SAVEPOINT
命令一塊兒使用,能夠把事務回滾到標記點,而不會管在此標記點以前的任何工做。SET TRANSACTION
:用來設置事務的隔離級別
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
COMMIT WORK
用來控制事務結束後的行爲是CHAIN
仍是RELEASE
。若是是CHAIN
方式,那麼事務就變成了鏈事務。
經過參數completion_type
來控制
COMMIT
是徹底等價的COMMIT AND CHAIN
,表示立刻自動開啓一個相同隔離級別的事務。COMMIT AND RELEASE
,表示事務提交以後會自動斷開與服務器的鏈接。QPS Question Per Second
:每秒請求數
TPS Transaction Per Second
:每秒事務處理能力
計算TPS的方法是(com_commit + com_rollback) / time。計算前提是全部事務必須是顯式提交的。
SQL標準定義的四個隔離級別爲:
READ UNCOMMITTED
:瀏覽訪問READ COMMITED
:遊標穩定REPEATABLE READ
:是2.9999°的隔離,沒有幻讀的保護。SERIALIZABLE
:稱爲隔離,或者3°的隔離。 MYSQL引擎默認支持的隔離級別是REPEATABLE READ
,可是已經經過Next-Key Lock
鎖的算法,避免了幻讀產生。達到SQL標準的SERIALIZABLE
隔離界別。
隔離級別越低,事務請求的鎖越少或者保持鎖的時間就越短。默認的事務隔離級別是READ COMMITTED
。
修改MYSQL啓動時設置的默認隔離級別,須要修改MYSQL的配置文件
[mysqld] transaction-isolation = READ-COMMITTED
如何查看事務隔離級別
# 當前會話的事務隔離級別 SELECT @@tx_isolation\G; # 全局的事務隔離級別 SELECT @@global.tx_isolation\G;
主從複製若是發生了不一致,可能發生的緣由有兩點:
READ COMMITTED
事務隔離級別下,事務沒有使用gap lock
進行鎖定,所以用戶在會話B中能夠在小於等於5的範圍內插入一條記錄。STATEMENT
格式記錄的是master
上產生的SQL語句,所以在master服務器上執行的順序爲先刪後插,可是STATEMENT
格式記錄的確實先插後刪。邏輯順序上產生了不一致。 INNODB存儲引擎提供了對XA事務的支持,並經過XA事務來支持分佈式事務的實現。分佈式事務指的是容許多個獨立的事務資源參與到一個全局的事務中。全局事務要求在其中的全部參與的事務要麼都提交,要麼都回滾。
INNODB存儲引擎的事務隔離級別必須設置爲SERIALIZABLE
。
XA事務容許不一樣數據庫以前的分佈式事務。分佈式事務可能在銀行的轉帳系統中比較常見。
XA事務由一個或多個資源管理器、一個事務管理器以及一個應用程序組成。
分佈式事務採用二段式提交的方式。
ROLLBACK
仍是COMMIT
。若是任何一個節點顯示不能提交,則全部的節點都被告知須要回滾。 MYSQL數據庫XA事務的SQL語法以下:
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
可是通常來講,單個節點上運行分佈式事務沒有實際意義,通常和變成語言來完成分佈式事務的操做。
@Data @Builder @NoArgsConstructor @AllArgsConstructor class MyXid implements Xid { public int formatId; public byte gtrid[]; public byte bqual[]; } public class xa_demo { public static MysqlXADataSource GetDataSource(String conString, String user, String passwd) { try { MysqlXADataSource ds = new MysqlXADataSource(); ds.setUrl(connString); ds.setUser(user); ds.setPassword(passwd); return ds; } catch(Exception e) { System.out.println(e.toString()); return null; } } } public static void main() { String connString1 = "jdbc:mysql://192.168.24.43:3306/bank_shanghai"; String connString2 = "jdbc:mysql://192.168.24.44:3306/bank_beijing"; try { MysqlXDataSource ds1 = GetDataSource(connString1, "mxr", "12345"); MysqlXDataSource ds2 = GetDataSource(connString2, "cxw", "12345"); XAConnection xaConn1 = ds1.getXAConnect(); XAResource xaRes1 = xaConn1.getXAResource(); Connection conn1 = xaConn1.getConnection(); Statement stmt1 = conn1.createStatement(); XAConnection xaConn2 = ds2.getXAConnect(); XAResource xaRes2 = xaConn2.getXAResource(); Connection conn2 = xaConn2.getConnection(); Statement stmt2 = conn2.createStatement(); Xid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02}); Xid xid2 = new MyXid(100, new byte[]{0x11}, new byte[]{0x12}); try { xaRes1.start(xid1, XAResource.TMNOFLAGS); stmt1.execute("UPDATE account SET money = money - 10000 where user = 'mxr'"); xaRes1.end(xid1, XAResource.TMSUCCESS); xaRes2.start(xid2, XAResource.TMNOFLAGS); stmt2.execute("UPDATE account SET money = money + 10000 where user = 'cxw'"); xaRes2.end(xid2, XAResource.TMSUCCESS); int ret2 = xaRes2.prepare(xid2); int ret1 = xaRes2.prepare(xid1); if( ret1 = XAResource.XA_OK && ret2 = XAResource.XA_OK) { xaRes1.commit(xid1, false); xaRes2.commit(xid2, false); } } catch(Exception e) { e.printStackTrace(); } } catch(Exception e) { System.out.println(e.toString()); } }
經過參數innodb_support_xa
能夠查看是否啓用了XA事務的支持(默認爲ON);
在MYSQL內,存儲引擎與插件之間,存儲引擎之間也存在一種分佈式事務,成爲內部XA事務。
最多見的是binlog
與INNODB
存儲引擎之間內部XA事務。binlog和存儲引擎的重作日誌必須同時寫入,保證原子性,不然會致使主備數據不一致。
CREATE PROCEDURE load(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a', 80); WHILE S <= count DO INSERT INTO t1 SELECT NULL, c; SET S = S + 1; END WHILE; END;
每次的insert都會發生自動提交,若是用戶插入10000條數據,在5000條是發生了錯誤,那這5000已存在的數據如何處理?
若是每次都提交,每次都須要寫重作日誌,影響效率。
因此建議使用同一個事務
CREATE PROCEDURE load(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a', 80); START TRANSACTION; WHILE S <= count DO INSERT INTO t1 SELECT NULL, c; SET S = S + 1; END WHILE; COMMIT; END;
編寫應用程序開發時,最好把事務的控制權限交給開發人員,在程序端進行事務的開始和結束。
使用自動回滾以後,MYSQL在程序段是不會拋出異常信息的,不便於調試,因此通常存儲過程當中值存放邏輯操做便可。管理操做所有放在java中進行。
長事務:執行時間較長的事務。
這邊通常會將長事務拆解爲多個小事務進行操做。這樣子,若是發生是失敗了,能夠繼續在失敗的小事務上繼續進行重試,而不用所有重試。節省時間。