MYSQL innodb中的只讀事物以及事物id的分配方式

gaopengtttt發表於2017-07-19
原創水平有限,如果有誤請指出


一、只讀事物
也許有人要問一個select算不算一個事物。其實在innodb中一個innodb的select是一個事物,他有trx_t結構體,並且放到了mysql_trx_list連結串列中,關於
innodb事物系統一級的事都做了,但是這種事物叫做只讀事物
bool read_only; /*!< true if transaction is flagged
as a READ-ONLY transaction.
if auto_commit && will_lock == 0
then it will be handled as a
AC-NL-RO-SELECT (Auto Commit Non-Locking
Read Only Select). A read only
transaction will not be assigned an
UNDO log. */
在實際的使用中他沒有自己的鎖結構也沒有自己的undo segment,這一點很好理解因為這個操作
始終是非鎖定的,至少在innodb一級是這樣(lock0lock.cc lock_table 都沒呼叫),但是在MYSQL中,我們會發現實際上select語句也會
獲得MDL LOCK。(再次宣告這裡只是說innodb select沒有表級別鎖存在,但是MYSQL上層會有MDL LOCK)
對於只讀事物原始碼註釋給出的流程如下:
Auto-commit non-locking read-only:
* NOT_STARTED -> ACTIVE -> NOT_STARTED
而我們一般的2pc TRX流程如下:
XA (2PC):
* NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED
可以看到他實際上就是沒有commit的步驟,沒有undo reodo這些當然是不需要的。但是不可否認它是一個事物
另外當需要一個事物的時候在現在innodb版本中呼叫如下:
trx_allocate_for_mysql --> trx_allocate_for_background --> trx_create_low 
這裡涉及到一個innodb 事物池的概念,trx_create_low 從事物池中取出一個事物TRX_T結構體指標給呼叫者
這個步驟完成後事物處於NOT_STARTED階段,這個時候TRX_T結構各種屬性都處於初始化階段,為什麼要說一下
事物池的概念因為後面說事物號分配的時候會用到這個概念。
然後根據呼叫者的需求適時啟用事物。實際上會呼叫,而呼叫會通過
trx_start_if_not_started_low->trx_start_low完成,在trx_start_low做好事物結構的準備工作,我們來看一
下關於原始碼中重點的部分


點選(此處)摺疊或開啟

  1. trx->read_only =
  2.         (trx->api_trx && !trx->read_write)
  3.         || (!trx->ddl && !trx->internal
  4.          && thd_trx_is_read_only(trx->mysql_thd))
  5.         || srv_read_only_mode; //此處獲取事物當前是否是隻讀屬性,可以看到他和我們的read_only引數設定事物是ddl事物是否是內部事物有關

  6.     if (!trx->auto_commit) { //是否自動提交否則需要設定will_Lock屬性如果時候只讀事物未TURE,如果是DML事物為flase
  7.      //這裡的auto_commit屬性和我們平時設定的引數感覺不是一回事
  8.         ++trx->will_lock;
  9.     } else if (trx->will_lock == 0) {
  10.         trx->read_only = true; //如果不需要will_lock屬性它肯定是隻讀事物
  11.     }
  12. //以上也就說明了只讀事物不需要鎖結構因為 trx->will_lock = 0(false)
  13.     /* We tend to over assert and that complicates the code somewhat.
  14.     e.g., the transaction state can be set earlier but we are forced to
  15.     set it under the protection of the trx_sys_t::mutex because some
  16.     trx list assertions are triggered unnecessarily. */

  17.     /* By default all transactions are in the read-only list unless they
  18.     are non-locking auto-commit read only transactions or background
  19.     (internal) transactions. Note: Transactions marked explicitly as
  20.     read only can write to temporary tables, we put those on the RO
  21.     list too. */
  22.     //當然如果是非只讀事物 我們需要開始分配undo rollback segment了 以及undo segment了
  23.     //並且trx->mysql_thd == 0 表示是否是MYSQL執行緒建立的innodb事物
  24.     //是否是讀寫事物這個是由呼叫者傳入只讀事物為false,DML事物為true,這裡的讀寫和前面
  25.     //trx->read_only有區別如果是隻讀事物建立臨時表也是讀寫事物
  26.     //是否是DDL事物 DDL也需要分配undo rollback segment了 以及undo segment
  27.     if (!trx->read_only
  28.      && (trx->mysql_thd == 0 || read_write || trx->ddl)) {

  29.         trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
  30.             srv_undo_logs, srv_undo_tablespaces,
  31.             TRX_RSEG_TYPE_REDO);

  32.         /* Temporary rseg is assigned only if the transaction
  33.         updates a temporary table */

  34.         trx_sys_mutex_enter();

  35.         trx_assign_id_for_rw(trx);//分配事物號
  36.         /*
  37.      (gdb) p trx_sys->max_trx_id
  38.         $21 = 328707
  39.      */

  40.         trx_sys_rw_trx_add(trx); //將入集合

  41.         ut_ad(trx->rsegs.m_redo.rseg != 0
  42.          || srv_read_only_mode
  43.          || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);

  44.         UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx); //將事物放入rw_trx_list

  45.         ut_d(trx->in_rw_trx_list = true);
  46. #ifdef UNIV_DEBUG
  47.         if (trx->id > trx_sys->rw_max_trx_id) {
  48.             trx_sys->rw_max_trx_id = trx->id;
  49.         }
  50. #endif /* UNIV_DEBUG */

  51.         trx->state = TRX_STATE_ACTIVE; //更改事物的狀態為ACTIVE

  52.         ut_ad(trx_sys_validate_trx_list());

  53.         trx_sys_mutex_exit();

  54.     } else {
  55.         trx->id = 0; //任然沒有分配事物ID給只讀事物

  56.         if (!trx_is_autocommit_non_locking(trx)) { //#define trx_is_autocommit_non_locking(t)     ((t)->auto_commit && (t)->will_lock == 0)

  57.             /* If this is a read-only transaction that is writing
  58.             to a temporary table then it needs a transaction id
  59.             to write to the temporary table. */
  60.             //如果是隻讀事物並且寫入了臨時表需要額外操作

  61.             if (read_write) {

  62.                 trx_sys_mutex_enter();

  63.                 ut_ad(!srv_read_only_mode);

  64.                 trx_assign_id_for_rw(trx);

  65.                 trx_sys->rw_trx_set.insert(
  66.                     TrxTrack(trx->id, trx));

  67.                 trx_sys_mutex_exit();
  68.             }

  69.             trx->state = TRX_STATE_ACTIVE;

  70.         } else {
  71.             ut_ad(!read_write);
  72.             trx->state = TRX_STATE_ACTIVE;
  73.         }
  74.     }

  75.     if (trx->mysql_thd != NULL) {
  76.         trx->start_time = thd_start_time_in_secs(trx->mysql_thd); //開始計時這是系統時間LINUX系統呼叫time()
  77.     } else {
  78.         trx->start_time = ut_time();
  79.     }

根據上面的註釋,我們可以看到只讀事物沒有分配undo segment也不會分配LOCK鎖結構
二、事物ID的分配
也許很多朋友不止我一個人在show engine innodb status的時候會看到如下兩種截然不同,相差很大的事物ID
(MYSQL)---TRANSACTION 329759, ACTIVE 10 sec
1 lock struct(s), heap size 1160, 0 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 140737154152192, query id 28 localhost root cleaning up
(MYSQL)---TRANSACTION 422212177398528, not started
0 lock struct(s), heap size 1160, 0 row lock(s)

這裡事物id 329759和422212177398528相差很大,innodb是怎麼分配的呢?
其實這裡實際上的事物id只有329759,及trx_t.id,是一個正常的DML事物,而對於 not started狀態的事物
以及只讀事物,是沒有事物id的其實就是0,但是show engine innodb status的時候會呼叫時候會簡單的分配
一個而已。
其實TRX ID的分配也是在trx_start_low中呼叫(trx_assign_id_for_rw(trx);//分配事物號 )
而對於只讀事物並不會分配(trx->id = 0; //任然沒有分配事物ID給只讀事物)都在上面的程式碼解釋中,
而show engine innodb status的時候對於事物ID為0的事物做如下輸出


點選(此處)摺疊或開啟

  1. UNIV_INLINE
  2. trx_id_t
  3. trx_get_id_for_print(
  4.     const trx_t*    trx)
  5. {
  6.     /* Readonly and transactions whose intentions are unknown (whether
  7.     they will eventually do a WRITE) don't have trx_t::id assigned (it is
  8.     0 for those transactions). Transaction IDs in
  9.     innodb_trx.trx_id,
  10.     innodb_locks.lock_id,
  11.     innodb_locks.lock_trx_id,
  12.     innodb_lock_waits.requesting_trx_id,
  13.     innodb_lock_waits.blocking_trx_id should match because those tables
  14.     could be used in an SQL JOIN on those columns. Also trx_t::id is
  15.     printed by SHOW ENGINE INNODB STATUS, and in logs, so we must have the
  16.     same value printed everywhere consistently. */
  17.   
  18.     /* DATA_TRX_ID_LEN is the storage size in bytes. */
  19.     static const trx_id_t    max_trx_id
  20.         = (1ULL << (DATA_TRX_ID_LEN * CHAR_BIT)) - 1;

  21.     ut_ad(trx->id <= max_trx_id);

  22.     return(trx->id != 0
  23.      ? trx->id
  24.      : reinterpret_cast<trx_id_t>(trx) | (max_trx_id + 1));
  25. }

我們從註釋也能看出 
Readonly and transactions whose intentions are unknown don't have trx_t::id assigned (it is 0 for those transactions)
實際上422212177398528這種id就是這裡列印的時候分配的,沒有什麼實際的意義
這裡的max_trx_id是一個常量281474976710655如果trx->id==0會呼叫
reinterpret_cast(trx) | (max_trx_id + 1)
將指標轉換為一個64位元組非負整數然後位或上(max_trx_id + 1),如下:
(gdb) p max_trx_id
$19 = 281474976710655
(gdb) p reinterpret_cast(trx)
$20 = 140737200690640
(gdb) p  reinterpret_cast(trx) | (max_trx_id + 1)
$21 = 422212177401296

而對於這裡DML的事物號的分配如下:

點選(此處)摺疊或開啟

  1. void
  2. trx_assign_id_for_rw(trx_t* trx)
  3. {
  4.     ut_ad(mutex_own(&trx_sys->mutex));

  5.     trx->id = trx->preallocated_id
  6.         ? trx->preallocated_id : trx_sys_get_new_trx_id();
  7.     //先判斷是否是這個事物分配過事物ID,因為從事物池中拿出來
  8.     //很可能以前用過,那麼就不需要再次分配了,否則新分配

  9.     if (trx->preallocated_id) { //如果是以前使用過的不一定是最大需要加入到vertor中間
  10.         // Maintain ordering in rw_trx_ids
  11.         trx_sys->rw_trx_ids.insert(
  12.             std::upper_bound(trx_sys->rw_trx_ids.begin(),
  13.                      trx_sys->rw_trx_ids.end(),
  14.                      trx->id), trx->id);
  15.     } else {
  16.         // The id is known to be greatest 新分配的肯定是最大 如果是最大加到某位即可
  17.         trx_sys->rw_trx_ids.push_back(trx->id);
  18.     }
  19. }
這裡涉及到事物池。
而對於trx_sys_get_new_trx_id如下:

點選(此處)摺疊或開啟

  1. trx_sys_get_new_trx_id()
  2. /*====================*/
  3. {
  4.     ut_ad(trx_sys_mutex_own());

  5.     /* VERY important: after the database is started, max_trx_id value is
  6.     divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
  7.     will evaluate to TRUE when this function is first time called,
  8.     and the value for trx id will be written to disk-based
  9.     Thus trx id values will not overlap when the database is
  10.     repeatedly */

  11.     if (!(trx_sys->max_trx_id % TRX_SYS_TRX_ID_WRITE_MARGIN)) {

  12.         trx_sys_flush_max_trx_id(); //TRX_SYS_TRX_ID_WRITE_MARGIN為256 如果trx_sys->max_trx_id達到256的整數倍需要刷盤
  13.          //到TRX_SYS_TRX_ID_STORE中.
  14.     }

  15.     return(trx_sys->max_trx_id++);//然後自身+1返回
  16. }

如此我們看到DML事物的事物ID是innodb分配的,而只讀事物或者not start事物的事物ID是在show engine的時候根據trx_t結構體
所在記憶體的指標演算法出來的,沒有實際的意義。

三、驗證只讀事物的存在
對於只讀事物我們在show engine innodb 只會列印出not start的事物或者活躍的已經獲得了鎖結構的事物一般是DML操作
但是可以再innodb_trx中觀察到,我這裡就簡單修改show engine innodb 原始碼列印輸出將只讀事物列印出來標記為RO TRX,
並且和innodb_trx對比

點選(此處)摺疊或開啟

  1. 下面是我修改後show engine innodb的輸出

  2. LIST OF TRANSACTIONS FOR EACH SESSION(1)(CHANGE BY GAOPENG ALL mysql_trx_list and rw_trx_list):
  3. (MYSQL)---TRANSACTION 422212177402680, ACTIVE 3 sec fetching rows
  4. mysql tables in use 1, locked 0
  5. 0 lock struct(s), heap size 1160, 0 row lock(s), RO TRX
  6. MySQL thread id 7, OS thread handle 140737153619712, query id 411 localhost root Sending data
  7. select * from test.tuser
這裡看到我們的只讀事物為RO TRX,lock struct(s)為0,沒有undo entries,因為有會列印出來。
再來看看innodb_trx的輸出:

點選(此處)摺疊或開啟

  1. mysql> select * from information_schema.innodb_trx \G
  2. *************************** 1. row ***************************
  3.                     trx_id: 422212177402680
  4.                  trx_state: RUNNING
  5.                trx_started: 2017-07-19 16:52:53
  6.      trx_requested_lock_id: NULL
  7.           trx_wait_started: NULL
  8.                 trx_weight: 0
  9.        trx_mysql_thread_id: 7
  10.                  trx_query: select * from test.tuser
  11.        trx_operation_state: fetching rows
  12.          trx_tables_in_use: 1
  13.          trx_tables_locked: 0
  14.           trx_lock_structs: 0
  15.      trx_lock_memory_bytes: 1160
  16.            trx_rows_locked: 0
  17.          trx_rows_modified: 0
  18.    trx_concurrency_tickets: 0
  19.        trx_isolation_level: REPEATABLE READ
  20.          trx_unique_checks: 1
  21.     trx_foreign_key_checks: 1
  22. trx_last_foreign_key_error: NULL
  23.  trx_adaptive_hash_latched: 0
  24.  trx_adaptive_hash_timeout: 0
  25.           trx_is_read_only: 1
  26. trx_autocommit_non_locking: 1
沒有問題都能觀察到,同樣我們也如我們所說只讀事物的事物ID是422212177402680,只是TRX_T結構體指標所在位置算出來的
演算法在上面。這裡注意事物也是有狀態標識的比如這裡的fetching rows。

四、其他
                                   
其實innodb中的事物比想象的要大很多,一個innodb的ddl是一個事物,一個innodb的select是一個事物,很多內部修改資料字典的操作也是一個事物
當然我們平時做的DML那更是事物了,上面說了只讀事物這裡簡單提一下ddl事物和內部事物。
這裡將trx_t結構體重關於他們標誌給出來:

innodb的ddl事物:
bool ddl; /*!< true if it is an internal transaction for DDL */
函式呼叫: trx_start_for_ddl_low-->trx_start_internal_low
可以看到一個ddl既是一個內部事物也是一個ddl事物

innodb的內部事物:
bool internal; /*!< true if it is a system/internal
transaction background task. This
includes DDL transactions too.  Such
transactions are always treated as
read-write. */
函式呼叫:trx_start_internal_low 典型的innodb修改資料字典就是internal事物

關於只讀事物實際上在官方手冊也有說明具體在

Optimizing InnoDB Read-Only Transactions 

我就不在說明什麼了。


作者微信:

MYSQL innodb中的只讀事物以及事物id的分配方式


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2142302/,如需轉載,請註明出處,否則將追究法律責任。

相關文章