日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mvcc原理_MVCC原理探究及MySQL源码实现分析

發布時間:2023/12/18 数据库 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mvcc原理_MVCC原理探究及MySQL源码实现分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

沃趣科技數據庫專家 ?董紅禹

MVCC原理探究及MySQL源碼實現分析

數據庫多版本讀場景

session 1

session 2

select a from test; return a = 10

start transaction;

update test set a = 20;

start transaction;

select a from test; return ?

commit;

select a from test; return ?

我們看下上面這個數據庫日常操作的例子。

session 1修改了一條記錄,沒有提交;與此同時,session 2 來查詢這條記錄,這時候返回記錄應該是多少呢?

session 1 提交之后 session 2 查詢出來的又應該是多少呢?

由于MySQL支持多種隔離級別,這個問題是需要看session2的事務隔離級別的,情況如下:

隔離級別為 READ-UNCOMMITTED 情況下:

session 1 commit前后 session 2 去查看都會看到的是修改后的結果 a = 20

隔離級別為 READ-COMMITTED 情況下:

session 1 commit 前查看到的還是 a =10 , commit之后看到的是 a = 20

隔離級別為 REPEATABLE-READ, SERIALIZABLE 情況下:

session 1 commit前后 session 2 去查看都會看到的是修改后的結果 a = 10

其實不管隔離級別,我們也拋開數據庫中的ACID,我們思考一個問題:眾所周知,InnoDB的數據都是存儲在B-tree里面的,修改后的數據到底要不要存儲在實際的B-tree葉子節點,session2是怎么做到查詢出來的結果還是10,而不是20呢?

MVCC實現原理

上述現象在數據庫中大家經常看到,但是數據庫到底是怎么實現的,深究的人就不多了。

其實原理很簡單,數據庫就是通過UNDO和MVCC來實現的。

通過DB_ROLL_PT 回溯查找數據歷史版本

首先InnoDB每一行數據還有一個DB_ROLL_PT的回滾指針,用于指向該行修改前的上一個歷史版本

當插入的是一條新數據時,記錄上對應的回滾段指針為NULL

更新記錄時,原記錄將被放入到undo表空間中,并通過DB_ROLL_PT指向該記錄。session2查詢返回的未修改數據就是從這個undo中返回的。MySQL就是根據記錄上的回滾段指針及事務ID判斷記錄是否可見,如果不可見繼續按照DB_ROLL_PT繼續回溯查找。

通過read view判斷行記錄是否可見

具體的判斷流程如下:

RR隔離級別下,在每個事務開始的時候,會將當前系統中的所有的活躍事務拷貝到一個列表中(read view)

RC隔離級別下,在每個語句開始的時候,會將當前系統中的所有的活躍事務拷貝到一個列表中(read view)

并按照以下邏輯判斷事務的可見性。

MVCC解決了什么問題

MVCC使得數據庫讀不會對數據加鎖,select不會加鎖,提高了數據庫的并發處理能力

借助MVCC,數據庫可以實現RC,RR等隔離級別,用戶可以查看當前數據的前一個或者前幾個歷史版本。保證了ACID中的I-隔離性。

MySQL代碼分析

前面我們介紹了什么是MVCC,以及它解決了什么問題。

下面我們來看一下在MySQL源碼中,到底是怎么實現這個邏輯的。

InnoDB隱藏字段源碼分析

InnoDB表中會存有三個隱藏字段,這三個字段是mysql默認幫我們添加的。我們可以通過代碼中查看到:

dict_table_add_system_columns(

/*==========================*/

dict_table_t*table,/*!< in/out: table */

mem_heap_t*heap)/*!< in: temporary heap */

{

ut_ad(table);

ut_ad(table->n_def==(table->n_cols-table->get_n_sys_cols()));

ut_ad(table->magic_n==DICT_TABLE_MAGIC_N);

ut_ad(!table->cached);

/* NOTE: the system columns MUST be added in the following order

(so that they can be indexed by the numerical value of DATA_ROW_ID,

etc.) and as the last columns of the table memory object.

The clustered index will not always physically contain all system

columns.

Intrinsic table don't need DB_ROLL_PTR as UNDO logging is turned off

for these tables. */

dict_mem_table_add_col(table,heap,"DB_ROW_ID",DATA_SYS,

DATA_ROW_ID|DATA_NOT_NULL,

DATA_ROW_ID_LEN);

#if (DATA_ITT_N_SYS_COLS != 2)

#error"DATA_ITT_N_SYS_COLS != 2"

#endif

#if DATA_ROW_ID != 0

#error"DATA_ROW_ID != 0"

#endif

dict_mem_table_add_col(table,heap,"DB_TRX_ID",DATA_SYS,

DATA_TRX_ID|DATA_NOT_NULL,

DATA_TRX_ID_LEN);

#if DATA_TRX_ID != 1

#error"DATA_TRX_ID != 1"

#endif

if(!table->is_intrinsic()){

dict_mem_table_add_col(table,heap,"DB_ROLL_PTR",DATA_SYS,

DATA_ROLL_PTR|DATA_NOT_NULL,

DATA_ROLL_PTR_LEN);

#if DATA_ROLL_PTR != 2

#error"DATA_ROLL_PTR != 2"

#endif

/* This check reminds that if a new system column is added to

the program, it should be dealt with here */

#if DATA_N_SYS_COLS != 3

#error"DATA_N_SYS_COLS != 3"

#endif

}

}

DB_ROW_ID:如果表中沒有顯示定義主鍵或者沒有唯一索引則MySQL會自動創建一個6字節的row id存在記錄中

DB_TRX_ID:事務ID

DB_ROLL_PTR:回滾段指針

InnoDB判斷事務可見性源碼分析

mysql中并不是根據事務的事務ID進行比較判斷記錄是否可見,而是根據每一行記錄上的事務ID進行比較來判斷記錄是否可見。

我們可以通過實驗驗證 , 創建一張表里面插入一條記錄

dhy@10.16.70.190:330612:25:47[dhy]>select*fromdhytest;

+------+

|id|

+------+

|10|

+------+

1rowinset(7.99sec)

手工開啟一個事務 更新一條記錄 但是并不提交:

dhy@10.10.80.199:330615:28:24[dhy]>update dhytestsetid=20;

QueryOK,3rows affected(40.71sec)

Rowsmatched:3Changed:3Warnings:0

在另外一個會話執行查詢

dhy@10.16.70.190:330612:38:33[dhy]>select*fromdhytest;

這時我們可以跟蹤調試mysql 查看他是怎么判斷記錄的看見性,中間函數調用太多列舉最重要部分

這里需要介紹一個重要的類 ReadView,Read View是事務開啟時當前所有事務的一個集合,這個類中存儲了當前Read View中最大事務ID及最小事務ID

/** The read should not see any transaction with trx id >= this

value. In other words, this is the "high water mark". */

trx_id_tm_low_limit_id;

/** The read should see all trx ids which are strictly

smaller (

low water mark". */

trx_id_tm_up_limit_id;

/** trx id of creating transaction, set to TRX_ID_MAX for free

views. */

trx_id_tm_creator_trx_id;

當我們執行上面的查詢語句時,跟蹤到主要函數如下:

函數row_search_mvcc->lock_clust_rec_cons_read_sees

bool

lock_clust_rec_cons_read_sees(

/*==========================*/

constrec_t*rec,/*!< in: user record which should be read or

passed over by a read cursor */

dict_index_t*index,/*!< in: clustered index */

constulint*offsets,/*!< in: rec_get_offsets(rec, index) */

ReadView*view)/*!< in: consistent read view */

{

ut_ad(index->is_clustered());

ut_ad(page_rec_is_user_rec(rec));

ut_ad(rec_offs_validate(rec,index,offsets));

/* Temp-tables are not shared across connections and multiple

transactions from different connections cannot simultaneously

operate on same temp-table and so read of temp-table is

always consistent read. */

//只讀事務或者臨時表是不需要一致性讀的判斷

if(srv_read_only_mode||index->table->is_temporary()){

ut_ad(view==0||index->table->is_temporary());

return(true);

}

/* NOTE that we call this function while holding the search

system latch. */

trx_id_ttrx_id=row_get_rec_trx_id(rec,index,offsets);//獲取記錄上的TRX_ID這里需要解釋下,我們一個查詢可能滿足的記錄數有多個。那我們每讀取一條記錄的時候就要根據這條記錄上的TRX_ID判斷這條記錄是否可見

return(view->changes_visible(trx_id,index->table->name));//判斷記錄可見性

}

下面是真正判斷記錄的看見性。

boolchanges_visible(

trx_id_tid,

consttable_name_t&name)const

MY_ATTRIBUTE((warn_unused_result))

{

ut_ad(id>0);

//如果ID小于Read View中最小的, 則這條記錄是可以看到。說明這條記錄是在select這個事務開始之前就結束的

if(id

return(true);

}

check_trx_id_sanity(id,name);

//如果比Read View中最大的還要大,則說明這條記錄是在事務開始之后進行修改的,所以此條記錄不應查看到

if(id>=m_low_limit_id){

return(false);

}elseif(m_ids.empty()){

return(true);

}

constids_t::value_type*p=m_ids.data();

return(!std::binary_search(p,p+m_ids.size(),id));//判斷是否在Read View中, 如果在說明在創建Read View時 此條記錄還處于活躍狀態則不應該查詢到,否則說明創建Read View是此條記錄已經是不活躍狀態則可以查詢到

}

對于不可見的記錄都是通過row_vers_build_for_consistent_read函數查詢UNDO構建老版本記錄,直到記錄可見。

這里需要說明一點 不同的事務隔離級別,可見性的實現也不一樣:

READ-COMMITTED

事務內的每個查詢語句都會重新創建Read View,這樣就會產生不可重復讀現象發生

REPEATABLE-READ

事務內開始時創建Read View , 在事務結束這段時間內 每一次查詢都不會重新重建Read View , 從而實現了可重復讀。

參考資料:

《唐成-2016PG大會-數據庫多版本實現內幕.pdf》

總結

以上是生活随笔為你收集整理的mvcc原理_MVCC原理探究及MySQL源码实现分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。