MySQL作爲一種關係型數據庫,已被廣泛應用到互聯網中的諸多項目中。今天我們來討論下事務的提交過程。
MySQL體系結構
由於mysql插件式存儲架構,導致開啓binlog後,事務提交實質是二階段提交,通過兩階段提交,來保證存儲引擎和二進制日誌的一致。
本文僅討論binlog未打卡狀態下的提交流程,後續會討論打開binlog選項後的提交邏輯。
測試環境
OS:WIN7
ENGINE:
bin-log:off
DB:
測試條件
set autocommit=0;
-- ---------------------------- -- Table structure for `user` -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(20) NOT NULL, `account` varchar(20) NOT NULL, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`), KEY `id` (`id`) USING BTREE, KEY `name` (`name`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
測試語句
insert into user values(1, 'sanzhang', '張三');
commit;
一般常用的DML:Data Manipulation Language 數據操縱語言,對錶的數據進行操作,(insert、update、delete )語句 和 DCL:Data Control Language 數據庫控制語言
(創建用戶、刪除用戶、授權、取消授權)語句 和 DDL:Data Definition Language 數據庫定義語言,對數據庫內部的對象進行創建、刪除、修改的操語句
,均是使用MySQL提供的公共接口mysql_execute_command,來執行相應的SQL語句。我們來分析下mysql_execute_command接口執行的流程:
mysql_execute_command { switch (command) { case SQLCOM_INSERT: mysql_insert(); break; case SQLCOM_UPDATE: mysql_update(); break; case SQLCOM_DELETE: mysql_delete(); break; ...... } if thd->is_error() //語句執行錯誤 trans_rollback_stmt(thd); else trans_commit_stmt(thd); }
從上述流程中,可以看到執行任何語句,最後都會執行trans_rollback_stmt或者trans_commit_stmt,這兩個分別是語句回滾和語句提交。
語句提交,對於非自動模式下,主要有兩個作用:
1、釋放autoinc鎖,這個鎖主要用來處理多個事務互斥的獲取自增序列。因此,無論最後執行的是語句提交還是語句回滾,該資源都是需要立馬釋放掉的。
2、標識語句在事務中的位置,方便語句級回滾。執行commit後,可以進入commit流程。
現在看下具體的事務提交流程:
mysql_execute_command trans_commit_stmt ha_commit_trans(thd, FALSE); { TC_LOG_DUMMY:ha_commit_low ha_commit_low() innobase_commit { //獲取innodb層對應的事務結構 trx = check_trx_exists(thd); if(單個語句,且非自動提交) { //釋放自增列佔用的autoinc鎖資源 lock_unlock_table_autoinc(trx); //標識sql語句在事務中的位置,方便語句級回滾 trx_mark_sql_stat_end(trx); } else 事務提交 { innobase_commit_low() { trx_commit_for_mysql(); <span style="color: #ff0000;">trx_commit</span>(trx); } //確定事務對應的redo日誌是否落盤【根據flush_log_at_trx_commit參數,確定redo日誌落盤方式】 trx_commit_complete_for_mysql(trx); trx_flush_log_if_needed_low(trx->commit_lsn); log_write_up_to(lsn); } } }
trx_commit trx_commit_low { trx_write_serialisation_history { trx_undo_update_cleanup //供purge線程處理,清理回滾頁 } trx_commit_in_memory { lock_trx_release_locks //釋放鎖資源 trx_flush_log_if_needed(lsn) //刷日誌 trx_roll_savepoints_free //釋放savepoints } }
MySQL是通過WAL方式,來保證數據庫事務的一致性和持久性,即ACID特性中的C(consistent)和D(durability)。
WAL(Write-Ahead Logging)是一種實現事務日誌的標準方法,具體而言就是:
1、修改記錄前,一定要先寫日誌;
2、事務提交過程中,一定要保證日誌先落盤,才能算事務提交完成。
通過WAL方式,在保證事務特性的情況下,可以提高數據庫的性能。
從上述流程可以看出,提交過程中,主要做了4件事情,
1、清理undo段信息,對於innodb存儲引擎的更新操作來說,undo段需要purge,這裏的purge主要職能是,真正刪除物理記錄。在執行delete或update操作時,實際舊記錄沒有真正刪除,只是在記錄上打了一個標記,而是在事務提交後,purge線程真正刪除,釋放物理頁空間。因此,提交過程中會將undo信息加入purge列表,供purge線程處理。
2、釋放鎖資源,mysql通過鎖互斥機制保證不同事務不同時操作一條記錄,事務執行後纔會真正釋放所有鎖資源,並喚醒等待其鎖資源的其他事務;
3、刷redo日誌,前面我們說到,mysql實現事務一致性和持久性的機制。通過redo日誌落盤操作,保證了即使修改的數據頁沒有即使更新到磁盤,只要日誌是完成了,就能保證數據庫的完整性和一致性;
4、清理保存點列表,每個語句實際都會有一個savepoint(保存點),保存點作用是爲了可以回滾到事務的任何一個語句執行前的狀態,由於事務都已經提交了,所以保存點列表可以被清理了。
關於mysql的鎖機制,purge原理,redo日誌,undo段等內容,其實都是數據庫的核心內容。
MySQL 本身不提供事務支持,而是開放了存儲引擎接口,由具體的存儲引擎來實現,具體來說支持 MySQL 事務的存儲引擎就是 InnoDB。
存儲引擎實現事務的通用方式是基於 redo log 和 undo log。
簡單來說,redo log 記錄事務修改後的數據, undo log 記錄事務前的原始數據。
所以當一個事務執行時實際發生過程簡化描述如下:
- 先記錄 undo/redo log,確保日誌刷到磁盤上持久存儲。
- 更新數據記錄,緩存操作並異步刷盤。
- 提交事務,在 redo log 中寫入 commit 記錄。
在 MySQL 執行事務過程中如果因故障中斷,可以通過 redo log 來重做事務或通過 undo log 來回滾,確保了數據的一致性。
這些都是由事務性存儲引擎來完成的,但 binlog 不在事務存儲引擎範圍內,而是由 MySQL Server 來記錄的。
那麼就必須保證 binlog 數據和 redo log 之間的一致性,所以開啓了 binlog 後實際的事務執行就多了一步,如下:
- 先記錄 undo/redo log,確保日誌刷到磁盤上持久存儲。
- 更新數據記錄,緩存操作並異步刷盤。
- 將事務日誌持久化到 binlog。
- 提交事務,在 redo log 中寫入commit記錄。
這樣的話,只要 binlog 沒寫成功,整個事務是需要回滾的,而 binlog 寫成功後即使 MySQL Crash 了都可以恢復事務並完成提交。
要做到這點,就需要把 binlog 和事務關聯起來,而只有保證了 binlog 和事務數據的一致性,才能保證主從數據的一致性。
所以 binlog 的寫入過程不得不嵌入到純粹的事務存儲引擎執行過程中,並以內部分佈式事務(xa 事務)的方式完成兩階段提交。
參考
1、《高性能MySQL》
出處:http://www.cnblogs.com/exceptioneye