mysql 事務 catch到異常 不提交,與RollBack()區別與過程分析

事務處理,是將操作事件交給數據庫(模擬)運行,直到commit操作,才使得修改實際產生效果,你可以看做是未提交事務都是處於一個臨時庫中進行
回滾是對於同一個事務,如果產生了錯誤,那麼取消這個臨時庫中的操作,不對實際數據產生影響

最主要的區別在於
如果不回滾,這些臨時操作會持續到這個個connection結束爲止,也就是雖然你看不到,但是臨時庫的操作依然存在,而回滾是即時生效,其實都是回滾了,只是時間點的不一樣.

如果使用了數據庫連接池,手動setAutoCommit(false),在catch{}裏面需要在rollBack();之後setAutoCommit(true),再回收該connection
源碼分析:
/**
* The method rollback() drops all changes made since the previous
* commit/rollback and releases any database locks currently held by the
* Connection.
*
* @exception SQLException
* if a database access error occurs
* @see commit
*/
public void rollback() throws SQLException {
synchronized (getConnectionMutex()) {
checkClosed();

        try {
            if (this.connectionLifecycleInterceptors != null) {
                IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) {

                    void forEach(Extension each) throws SQLException {
                        if (!((ConnectionLifecycleInterceptor)each).rollback()) {
                            this.stopIterating = true;
                        }
                    }
                };

                iter.doForAll();

                if (!iter.fullIteration()) {
                    return;
                }
            }
            // no-op if _relaxAutoCommit == true
            if (this.autoCommit && !getRelaxAutoCommit()) {
                throw SQLError.createSQLException(
                        "Can't call rollback when autocommit=true",
                        SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor());
            } else if (this.transactionsSupported) {
                try {
                    rollbackNoChecks();
                } catch (SQLException sqlEx) {
                    // We ignore non-transactional tables if told to do so
                    if (getIgnoreNonTxTables()
                            && (sqlEx.getErrorCode() == SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
                        return;
                    }
                    throw sqlEx;

                }
            }
        } catch (SQLException sqlException) {
            if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE
                    .equals(sqlException.getSQLState())) {
                throw SQLError.createSQLException(
                        "Communications link failure during rollback(). Transaction resolution unknown.",
                        SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor());
            }

            throw sqlException;
        } finally {
            this.needsPing = this.getReconnectAtTxEnd();
        }
    }
}

只允許拿到連接互斥鎖的線程進入方法。
檢查連接是否被強制關閉,若被強制關閉,則拋出”No operations allowed after connection closed.” 異常。
如果autoCommit且relaxAutoCommit爲false,則拋出”Can’t call commit when autocommit=true”異常。
如果使用本地事務狀態以及MySql的版本號至少大於5.0.0並且事務在服務器上。
如果支持事務,則執行。
執行rollback。

    private void rollbackNoChecks() throws SQLException {
        if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) {
            if (!this.io.inTransactionOnServer()) {
                return; // effectively a no-op
            }
        }

        execSQL(null, "rollback", -1, null,
                DEFAULT_RESULT_SET_TYPE,
                DEFAULT_RESULT_SET_CONCURRENCY, false,
                this.database, null, false);
    }

mysql_Innodb的undo_log和redo_log
內存緩衝池

buffer pool如果mysql不用內存緩衝池,每次讀寫數據時,都需要訪問磁盤,必定會大大增加I/O請求,導致效率低下。所以Innodb引擎在讀寫數據時,把相應的數據和索引載入到內存中的緩衝池(buffer pool)中,一定程度的提高了數據讀寫的速度。
buffer pool:佔最大塊內存,用來存放各種數據的緩存包括有索引頁、數據頁、undo頁、插入緩衝、自適應哈希索引、innodb存儲的鎖信息、數據字典信息等。工作方式總是將數據庫文件按頁(每頁16k)讀取到緩衝池,然後按最近最少使用(lru)的算法來保留在緩衝池中的緩存數據。如果數據庫文件需要修改,總是首先修改在緩存池中的頁(發生修改後即爲髒頁dirty page),然後再按照一定的頻率將緩衝池的髒頁刷新到文件。
表空間

表空間可看做是InnoDB存儲引擎邏輯結構的最高層。 表空間文件:InnoDB默認的表空間文件爲ibdata1。
段:表空間由各個段組成,常見的段有數據段、索引段、回滾段(undo log段)等。
區:由64個連續的頁組成,每個頁大小爲16kb,即每個區大小爲1MB。
頁:每頁16kb,且不能更改。常見的頁類型有:數據頁、Undo頁、系統頁、事務數據頁、插入緩衝位圖頁、插入緩衝空閒列表頁、未壓縮的二進制大對象頁、壓縮的二進制大對象頁。
redo log 和undo log

爲了滿足事務的持久性,防止buffer pool數據丟失,innodb引入了redo log。爲了滿足事務的原子性,innodb引入了undo log。
redo log

redo log就是保存執行的SQL語句到一個指定的Log文件,當mysql執行數據恢復時,重新執行redo log記錄的SQL操作即可。引入buffer pool會導致更新的數據不會實時持久化到磁盤,當系統崩潰時,雖然buffer pool中的數據丟失,數據沒有持久化,但是系統可以根據Redo Log的內容,將所有數據恢復到最新的狀態。redo log在磁盤上作爲一個獨立的文件存在。默認情況下會有兩個文件,名稱分別爲 ib_logfile0和ib_logfile1。
參數innodb_log_file_size指定了redo log的大小;innodb_log_file_in_group指定了redo log的數量,默認爲2; innodb_log_group_home_dir指定了redo log所在路徑。
innodb_additional_mem_pool_size = 100M
innodb_buffer_pool_size = 128M
innodb_data_home_dir = /home/mysql/local/mysql/var
innodb_data_file_path = ibdata1:1G:autoextend
innodb_file_io_threads = 4
innodb_thread_concurrency = 16
innodb_flush_log_at_trx_commit = 1

innodb_log_buffer_size = 8M
innodb_log_file_size = 128M
innodb_log_file_in_group = 2
innodb_log_group_home_dir = /home/mysql/local/mysql/var
undo log

爲了滿足事務的原子性,在操作任何數據之前,首先將數據備份到Undo Log,然後進行數據的修改。如果出現了錯誤或者用戶執行了ROLLBACK語句,系統可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態。與redo log不同的是,磁盤上不存在單獨的undo log文件,它存放在數據庫內部的一個特殊段(segment)中,這稱爲undo段(undo segment),undo段位於共享表空間內。
Innodb爲每行記錄都實現了三個隱藏字段:
6字節的事務ID(DB_TRX_ID)
7字節的回滾指針(DB_ROLL_PTR)
隱藏的ID
redo log的記錄內容

undo log和 redo log本身是分開的。innodb的undo log是記錄在數據文件(ibd)中的,而且innodb將undo log的內容看作是數據,因此對undo log本身的操作(如向undo log中插入一條undo記錄等),都會記錄redo log。undo log可以不必立即持久化到磁盤上。即便丟失了,也可以通過redo log將其恢復。因此當插入一條記錄時:
向undo log中插入一條undo log記錄。
向redo log中插入一條”插入undo log記錄“的redo log記錄。
插入數據。
向redo log中插入一條”insert”的redo log記錄。
redo log的io性能

爲了保證Redo Log能夠有比較好的IO性能,InnoDB 的 Redo Log的設計有以下幾個特點:
儘量保持Redo Log存儲在一段連續的空間上。因此在系統第一次啓動時就會將日誌文件的空間完全分配。以順序追加的方式記錄Redo Log。
批量寫入日誌。日誌並不是直接寫入文件,而是先寫入redo log buffer,然後每秒鐘將buffer中數據一併寫入磁盤
併發的事務共享Redo Log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一起,以減少日誌佔用的空間。
Redo Log上只進行順序追加的操作,當一個事務需要回滾時,它的Redo Log記錄也不會從Redo Log中刪除掉
redo & undo log的作用

數據持久化
buffer pool中維護一個按髒頁修改先後順序排列的鏈表,叫flush_list。根據flush_list中頁的順序刷數據到持久存儲。按頁面最早一次被修改的順序排列。正常情況下,dirty page什麼時候flush到磁盤上呢?
當redo空間佔滿時,將會將部分dirty page flush到disk上,然後釋放部分redo log。
當需要在Buffer pool分配一個page,但是已經滿了,這時候必須 flush dirty pages to disk。一般地,可以通過啓動參數 innodb_max_dirty_pages_pct控制這種情況,當buffer pool中的dirty page到達這個比例的時候,把dirty page flush到disk中。
檢測到系統空閒的時候,會flush。
數據恢復
隨着時間的積累,Redo Log會變的很大。如果每次都從第一條記錄開始恢復,恢復的過程就會很慢,從而無法被容忍。爲了減少恢復的時間,就引入了Checkpoint機制。假設在某個時間點,所有的髒頁都被刷新到了磁盤上。這個時間點之前的所有Redo Log就不需要重做了。系統記錄下這個時間點時redo log的結尾位置作爲checkpoint。在進行恢復時,從這個checkpoint的位置開始即可。Checkpoint點之前的日誌也就不再需要了,可以被刪除掉。
事務回滾
rollback1F1~F6是某行列的名字,1~6是其對應的數據。後面三個隱含字段分別對應該行的事務號和回滾指針。假如這條數據是剛INSERT的,可以認爲ID爲1,其他兩個字段爲空。
舉例說明數據行更新以及回滾的過程:
事務1:更改某行數據的值
當事務1更改該行的值時,會進行如下操作:
用排他鎖鎖定該行
把該行修改前的值Copy到undo log,即下圖中下面的行
修改當前行的值,填寫事務編號,使回滾指針指向undo log中的修改前的行
記錄redo log
事務2:再次更改該行數據的值rollback2
與事務1相同,此時undo log中有兩行記錄,並且通過回滾指針連在一起。因此,如果undo log一直不刪除,則會通過當前記錄的回滾指針回溯到該行創建時的初始內容。在Innodb中存在purge線程,它會查詢那些比現在最老的活動事務還早的undo log,並刪除它們,從而保證undo log文件不至於無限增長。
回滾過程
根據當前回滾指針從undo log中找出事務修改前的版本,並恢復。如果事務影響的行非常多,回滾則可能會變的效率不高。當事務行數在1000~10000之 間,Innodb效率還是非常高的。
Innodb也會將事務回滾時的操作也記錄到redo log中。回滾操作本質上也是對數據進行修改,因此回滾時對數據的操作也會記錄到Redo Log中。
一個回滾過程的redo log 看起來是這樣的:
記錄1: <trx1, Undo log insert >
記錄2: <trx1, insert A…>
記錄3: <trx1, Undo log insert >
記錄4: <trx1, update B…>
記錄5: <trx1, Undo log insert >
記錄6: <trx1, delete C…>
記錄7: <trx1, insert C>
記錄8: <trx1, update B to old value>
記錄9: <trx1, delete A>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章