1. 程式人生 > >mysql 事務 catch到異常 不提交,與RollBack()區別與過程分析

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>