91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

基于WRITESET的并行復制方式

發布時間:2020-03-07 17:45:55 來源:網絡 閱讀:274 作者:艾弗森哇 欄目:MySQL數據庫

  基于COMMIT_ORDER的并行復制只有在有壓力的情況下才可能會形成一組,壓力不大的情況下在從庫的并行度并不會高。但是基于WRITESET的并行復制目標就是在ORDER_COMMIT的基礎上再盡可能的降低last commit,這樣在從庫獲得更好的并行度(即便在主庫串行執行的事務在從庫也能并行應用)。它使用的方式就是通過掃描Writeset中的每一個元素(行數據的hash值)在一個叫做Writeset的歷史MAP(行數據的hash值和seq number的一個MAP)中進行比對,尋找是否有沖突的行,然后做相應的處理,后面我們會詳細描述這種行為。如果要使用這種方式我們需要在主庫設置如下兩個參數:

  transaction_write_set_extraction=XXHASH64

  binlog_transaction_dependency_tracking=WRITESET

  它們是在5.7.22才引入的。

  一、奇怪的last commit

  我們先來看一個截圖,仔細觀察其中的last commit:

  

基于WRITESET的并行復制方式


  我們可以看到其中的last commit看起來是亂序的,這種情況在基于COMMIT_ORDER 的并行復制方式下是不可能出現的。實際上它就是我們前面說的基于WRITESET的并行復制再盡可能降低的last commit的結果。這種情況會在MTS從庫獲得更好的并行回放效果,第19節將會詳細解釋并行判定的標準。

  二、Writeset是什么

  實際上Writeset是一個集合,使用的是C++ STL中的set容器,在類Rpl_transaction_write_set_ctx中包含了如下定義:

  std::set write_set_unique;

  集合中的每一個元素都是hash值,這個hash值和我們的transaction_write_set_extraction參數指定的算法有關,其來源就是行數據的主鍵和唯一鍵。每行數據包含了兩種格式:

  字段值為二進制格式

  字段值為字符串格式

  每行數據的具體格式為:

  

基于WRITESET的并行復制方式


  在Innodb層修改一行數據之后會將這上面的格式的數據進行hash后寫入到Writeset中。可以參考函數add_pke,后面我也會以偽代碼的方式給出部分流程。

  但是需要注意一個事務的所有的行數據的hash值都要寫入到一個Writeset。如果修改的行比較多那么可能需要更多內存來存儲這些hash值。雖然8字節比較小,但是如果一個事務修改的行很多,那么還是需要消耗較多的內存資源的。

  為了更直觀的觀察到這種數據格式,可以使用debug的方式獲取。下面我們來看一下。

  三、Writeset的生成

  我們使用如下表:

  mysql> use testDatabase changedmysql> show create table jj10 \G*************************** 1. row *************************** Table: jj10Create Table: CREATE TABLE `jj10` ( `id1` int(11) DEFAULT NULL, `id2` int(11) DEFAULT NULL, `id3` int(11) NOT NULL, PRIMARY KEY (`id3`), UNIQUE KEY `id1` (`id1`), KEY `id2` (`id2`)) ENGINE=InnoDB DEFAULT CHARSET=latin11 row in set (0.00 sec)

  我們寫入一行數據:

  insert into jj10 values(36,36,36);

  這一行數據一共會生成4個元素分別為:

  注意:這里顯示的?是分隔符

  1. 主鍵二進制格式

  (gdb) p pke$1 = "PRIMARY?test?4jj10?4\200\000\000$?4"**注意:\200\000\000$ :為3個八進制字節和ASCII字符 $,其轉換為16進制就是“0X80 00 00 24 ”**

  分解為:

  

基于WRITESET的并行復制方式


  2. 主鍵字符串格式:

  (gdb) p pke$2 = "PRIMARY?test?4jj10?436?2"

  分解為:

  

基于WRITESET的并行復制方式


  3. 唯一鍵二進制格式

  (gdb) p pke$3 = "id1?test?4jj10?4\200\000\000$?4"

  解析同上

  4. 唯一鍵字符串格式:

  (gdb) p pke$4 = "id1?test?4jj10?436?2"

  解析同上

  最終這些數據會通過hash算法后寫入到Writeset中。

  四、函數add_pke的大概流程

  下面是一段偽代碼,用來描述這種生成過程:

  如果表中存在索引: 將數據庫名,表名信息寫入臨時變量 循環掃描表中每個索引: 如果不是唯一索引: 退出本次循環繼續循環。 循環兩種生成數據的方式(二進制格式和字符串格式): 將索引名字寫入到pke中。 將臨時變量信息寫入到pke中。 循環掃描索引中的每一個字段: 將每一個字段的信息寫入到pke中。 如果字段掃描完成: 將pke生成hash值并且寫入到寫集合中。 如果沒有找到主鍵或者唯一鍵記錄一個標記,后面通過這個標記來 判定是否使用Writeset的并行復制方式

  五、Writeset設置對last commit的處理方式

  前一節我們討論了基于ORDER_COMMIT的并行復制是如何生成last_commit和seq number的。實際上基于WRITESET的并行復制方式只是在ORDER_COMMIT的基礎上對last_commit做更進一步處理,并不影響原有的ORDER_COMMIT邏輯,因此如果要回退到ORDER_COMMIT邏輯非常方便。可以參考MYSQL_BIN_LOG::write_gtid函數。

  根據binlog_transaction_dependency_tracking取值的不同會做進一步的處理,如下:

  ORDER_COMMIT:調用m_commit_order.get_dependency函數。這是前面我們討論的方式。

  WRITESET:調用m_commit_order.get_dependency函數,然后調用m_writeset.get_dependency。可以看到m_writeset.get_dependency函數會對原有的last commit做處理。

  WRITESET_SESSION:調用m_commit_order.get_dependency函數,然后調用m_writeset.get_dependency再調用m_writeset_session.get_dependency。

  m_writeset_session.get_dependency會對last commit再次做處理。

  這段描述的代碼對應:

  case DEPENDENCY_TRACKING_COMMIT_ORDER: m_commit_order.get_dependency(thd, sequence_number, commit_parent); break; case DEPENDENCY_TRACKING_WRITESET: m_commit_order.get_dependency(thd, sequence_number, commit_parent); m_writeset.get_dependency(thd, sequence_number, commit_parent); break; case DEPENDENCY_TRACKING_WRITESET_SESSION: m_commit_order.get_dependency(thd, sequence_number, commit_parent); m_writeset.get_dependency(thd, sequence_number, commit_parent); m_writeset_session.get_dependency(thd, sequence_number, commit_parent); break;

  六、Writeset的歷史MAP

  我們到這里已經討論了Writeset是什么,也已經說過如果要降低last commit的值我們需要通過對事務的Writeset和Writeset的歷史MAP進行比對,看是否沖突才能決定降低為什么值。那么必須在內存中保存一份這樣的一個歷史MAP才行。在源碼中使用如下方式定義:

  /* Track the last transaction sequence number that changed each row in the database, using row hashes from the writeset as the index. */ typedef std::map Writeset_history; //map實現 Writeset_history m_writeset_history;

  我們可以看到這是C++ STL中的map容器,它包含兩個元素:

  Writeset的hash值

  最新一次本行數據修改事務的seq number

  它是按照Writeset的hash值進行排序的。

  其次內存中還維護一個叫做m_writeset_history_start的值,用于記錄Writeset的歷史MAP中最早事務的seq number。如果Writeset的歷史MAP滿了就會清理這個歷史MAP然后將本事務的seq number寫入m_writeset_history_start,作為最早的seq number。后面會看到對于事務last commit的值的修改總是從這個值開始然后進行比較判斷修改的,如果在Writeset的歷史MAP中沒有找到沖突那么直接設置last commit為這個m_writeset_history_start值即可。下面是清理Writeset歷史MAP的代碼:

  if (exceeds_capacity || !can_use_writesets)//Writeset的歷史MAP已滿 { m_writeset_history_start= sequence_number;//如果超過最大設置,清空writeset history。//從當前seq number 重新記錄, 也就是最小的那個事務seq number m_writeset_history.clear();//清空歷史MAP }

  七、Writeset的并行復制對last commit的處理流程

  這里介紹一下整個處理的過程,假設如下:

  當前通過基于ORDER_COMMIT的并行復制方式后,構造出來的是(last commit=125,seq number=130)。

  本事務修改了4條數據,我分別使用ROW1/ROW7/ROW6/ROW10代表。

  表只包含主鍵沒有唯一鍵,并且我的圖中只保留行數據的二進制格式的hash值,而沒有包含數據的字符串格式的hash值。

  初始化情況如下圖:?

 

基于WRITESET的并行復制方式


  第一步 設置last commit為writeset_history_start的值也就是100。

  第二步 ROW1.HASHVAL在Writeset歷史MAP中查找,找到沖突的行ROW1.HASHVAL將歷史MAP中這行數據的seq number更改為130。同時設置last commit為120。

  第三步 ROW7.HASHVAL在Writeset歷史MAP中查找,找到沖突的行ROW7.HASHVAL將Writeset歷史MAP中這行數據的seq number更改為130。由于歷史MAP中對應的seq number為114,小于120不做更改。last commit依舊為120。

  第四步 ROW6.HASHVAL在Writeset歷史MAP中查找,找到沖突的行ROW6.HASHVAL將Writeset歷史MAP中這行數據的seq number更改為130。由于歷史MAP中對應的seq number為105,小于120不做更改。last commit依舊為120。

  第五步 ROW10.HASHVAL在Writeset歷史MAP中查找,沒有找到沖突的行,因此需要將這一行插入到Writeset歷史MAP中查找(需要判斷是否導致歷史MAP占滿,如果占滿則不需要插入,后面隨即要清理掉)。即要將ROW10.HASHVAL和seq number=130插入到Writeset歷史MAP中。

  整個過程結束。last commit由以前的130降低為120,目的達到了。實際上我們可以看出Writeset歷史MAP就相當于保存了一段時間以來修改行的快照,如果保證本次事務修改的數據在這段時間內沒有沖突,那么顯然是可以在從庫并行執行的。last commit降低后如下圖:

  鄭州不孕不育醫院:http://wapyyk.39.net/zz3/zonghe/1d427.html

基于WRITESET的并行復制方式


  整個邏輯就在函數Writeset_trx_dependency_tracker::get_dependency中,下面是一些關鍵代碼,代碼稍多:

  if (can_use_writesets) //如果能夠使用writeset 方式 { /* Check if adding this transaction exceeds the capacity of the writeset history. If that happens, m_writeset_history will be cleared only after 而 add_pke using its information for current transaction. */ exceeds_capacity= m_writeset_history.size() + writeset->size() > m_opt_max_history_size;//如果大于參數binlog_transaction_dependency_history_size設置清理標記 /* Compute the greatest sequence_number among all conflicts and add the transaction's row hashes to the history. */ int64 last_parent= m_writeset_history_start;//臨時變量,首先設置為最小的一個seq number for (std::set::iterator it= writeset->begin(); it != writeset->end(); ++it)//循環每一個Writeset中的每一個元素 { Writeset_history::iterator hst= m_writeset_history.find(*it);//是否在writeset history中 已經存在了。//map中的元素是 key是writeset 值是sequence number if (hst != m_writeset_history.end()) //如果存在 { if (hst->second > last_parent && hst->second < sequence_number) last_parent= hst->second;//如果已經大于了不需要設置 hst->second= sequence_number;//更改這行記錄的sequence_number } else { if (!exceeds_capacity) m_writeset_history.insert(std::pair(*it, sequence_number));//沒有沖突則插入。 } }...... if (!write_set_ctx->get_has_missing_keys())//如果沒有主鍵和唯一鍵那么不更改last commit { /* The WRITESET commit_parent then becomes the minimum of largest parent found using the hashes of the row touched by the transaction and the commit parent calculated with COMMIT_ORDER. */; commit_parent= std::min(last_parent, commit_parent);//這里對last commit做更改了。降低他的last commit } } } } if (exceeds_capacity || !can_use_writesets) { m_writeset_history_start= sequence_number;//如果超過最大設置 清空writeset history。//從當前sequence 重新記錄 也就是最小的那個事務seqnuce number m_writeset_history.clear();//清空整個MAP }

  八、WRITESET_SESSION的方式

  前面說過這種方式就是在WRITESET的基礎上繼續處理,實際上它的含義就是同一個session的事務不允許在從庫并行回放。代碼很簡單,如下:

  int64 session_parent= thd->rpl_thd_ctx.dependency_tracker_ctx(). get_last_session_sequence_number();//取本session的上一次事務的seq number if (session_parent != 0 && session_parent < sequence_number)//如果本session已經做過事務并且本次當前的seq number大于上一次的seq number commit_parent= std::max(commit_parent, session_parent);//說明這個session做過多次事務不允許并發,修改為order_commit生成的last commit thd->rpl_thd_ctx.dependency_tracker_ctx(). set_last_session_sequence_number(sequence_number);//設置session_parent的值為本次seq number的值

  經過這個操作后,我們發現這種情況最后last commit恢復成了ORDER_COMMIT的方式。

  九、關于binlog_transaction_dependency_history_size參數說明

  本參數默認值為25000。代表的是我們說的Writeset歷史MAP中元素的個數。如前面分析的Writeset生成過程中修改一行數據可能會生成多個HASH值,因此這個值還不能完全等待于修改的行數,可以理解為如下:

  binlog_transaction_dependency_history_size/2=修改的行數 * (1+唯一鍵個數)

  我們通過前面的分析可以發現如果這個值越大那么在Writeset歷史MAP中能容下的元素也就越多,生成的last commit就可能更加精確(更加小),從庫并發的效率也就可能越高。但是我們需要注意設置越大相應的內存需求也就越高了。

  十、沒有主鍵的情況

  實際上在函數add_pke中就會判斷是否有主鍵或者唯一鍵,如果存在唯一鍵也是可以。Writeset中存儲了唯一鍵的行數據hash值。參考函數add_pke,下面是判斷:

  if (!((table->key_info[key_number].flags & (HA_NOSAME )) == HA_NOSAME))//跳過非唯一的KEY continue;

  如果沒有主鍵或者唯一鍵那么下面語句將被觸發:

  if (writeset_hashes_added == 0) ws_ctx->set_has_missing_keys();

  然后我們在生成last commit會判斷這個設置如下:

  if (!write_set_ctx->get_has_missing_keys())//如果沒有主鍵和唯一鍵那么不更改last commit { /* The WRITESET commit_parent then becomes the minimum of largest parent found using the hashes of the row touched by the transaction and the commit parent calculated with COMMIT_ORDER. */; //這里對last commit做更改了。降低他的last commit commit_parent= std::min(last_parent, commit_parent); } }

  因此沒有主鍵可以使用唯一鍵,如果都沒有的話WRITESET設置就不會生效回退到老的ORDER_COMMIT方式。

  十一、為什么同一個session執行的事務也能生成同樣的last commit

  有了前面的基礎,我們就很容易解釋這種現象了。其主要原因就是Writeset的歷史MAP的存在,只要這些事務修改的行沒有沖突,也就是主鍵/唯一鍵不相同,那么在基于WRITESET的并行復制方式中就可以存在這種現象,但是如果binlog_transaction_dependency_tracking設置為WRITESET_SESSION則不會出現這種現象。

  寫在最后

  好了到這里我們明白了基于WRITESET的并行復制方式的優點,但是它也有明顯的缺點如下:

  Writeset中每個hash值都需要和Writeset的歷史MAP進行比較。

  Writeset需要額外的內存空間。

  Writeset的歷史MAP需要額外的內存空間。

  如果從庫沒有延遲,則不需要考慮這種方式,即便有延遲我們也應該先考慮其他方案。下一次我們將會描述有哪些導致延遲的可能。


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乐东| 屏南县| 红安县| 舒兰市| 疏勒县| 温泉县| 伊金霍洛旗| 江孜县| 奉化市| 黎城县| 固安县| 庆阳市| 安龙县| 洞头县| 湟源县| 潞西市| 中牟县| 霸州市| 万州区| 大连市| 蒲城县| 安吉县| 崇州市| 延长县| 郑州市| 高要市| 哈尔滨市| 两当县| 闵行区| 微山县| 酉阳| 曲麻莱县| 林西县| 老河口市| 汉中市| 新乐市| 湘西| 彰化县| 个旧市| 凤翔县| 南漳县|