MVCC&PURGE&分佈式事務

Ⅰ、MVCC介紹

consistent non-locking read,經過行多版本控制的方式讀取當前執行時間點的記錄mysql

默認狀況下innodb select沒有任何鎖,讀到的記錄在更新就經過undo讀以前版本,serializable時候讀會被阻塞,由於它默認加一個lock in share mode
--->like oraclegit

原理
undo && read_view
經過read_view判斷一條記錄是否可見,不可見(在更新被鎖住)就經過undo回滾到以前版本,以前的版本再讀trx_id,還不可見再回滾,rc只要回滾一個版本,rr可能要回滾不少版本,最大trx_id是持久化的,保存在共享表空間中github

其實理解下就是事務在不在活躍列表中,在的話這個事務對記錄作的動做就不可見須要找記錄的前鏡像(rc老是讀最新的非鎖定版本,rr老是讀最老的非鎖定版本,後續會有專門的文章說明)sql

舉個栗子數據庫

session1:
(root@localhost) [test]> select * from t;
+------+
| a    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> update t set a = a + 1 where a = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(root@localhost) [test]> select * from t;
+------+
| a    |
+------+
|    2 |
+------+
1 row in set (0.00 sec)

session2:
(root@localhost) [test]> begin;
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> select * from t;
+------+
| a    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

第一個會話開啓事務更新記錄,不提交,此時記錄是被鎖住的編程

新開一個會話去select 這條記錄,並不會由於有鎖而阻塞,讀到的是原來的記錄session

此時commit以後,以前版本的undo是不能被立刻回收的,由於其餘線程可能還在引用以前版本的undo,真正的回收undo是purge線程作的mvc

Ⅱ、purge線程

2.1 purge介紹

purge的做用是刪除undo,真正刪除一條記錄(完成update和delete)oracle

delete from table where pk=1;

在page中只是標記爲刪除,page上並無真正的刪除
相關參數:innodb_purge_threads    默認是1,5.7中設大一點,4或者8,都是ssd性能比較好
  • 5.5以前全部的purge操做都是master thread作的分佈式

    默認只有一個purge thread

    innodb_purge_threads={0|1}
  • 5.6

    N purge thread

    innodb_purge_threads={4}

2.2 purge具體過程

1024個槽------1024個undo回滾段,每一個槽對應不一樣的undo日誌

一旦事務提交,undo就放到hitory list中

tips:

由於記錄不是有序的,因此purge操做須要大量離散讀取操做

2.3 線上常見問題

undo不斷增大,不能有效回收,致使系統空間不斷增大,

最主要的緣由有兩個:

  • 索引沒有添加

    檢查slow log
  • 存在大事務

    拆大爲小

其實就一點,一個事務執行時間很長,那對應的undo就不能回收,至少要commit完成後才能回收

另外回滾比提交慢很是多,commit很快,rollback須要的時間就是事務執行的時間,邏輯回滾

tips:

目前MySQL已經支持在線回收undo,詳見阿里數據庫內核月報

Ⅲ、分佈式事務

以前咱們談到binlog和redo的一致性是經過一個內部的xa事務保證的,這裏簡單聊下外部的分佈式事務

3.1 看下簡單語法

(root@localhost) [test]> xa start 'a';    -- 開啓一個分佈式事務
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> insert into t values(2000);
Query OK, 1 row affected (0.09 sec)

(root@localhost) [test]> insert into t values(3000);
Query OK, 1 row affected (0.00 sec)

(root@localhost) [test]> xa end 'a';  -- 結束
Query OK, 0 rows affected (0.00 sec)

(root@localhost) [test]> xa prepare 'a';  -- 寫prepare
Query OK, 0 rows affected (0.03 sec)

(root@localhost) [test]> xa recover;  -- 看一眼,有一個分佈式事務
+----------+--------------+--------------+------+
| formatID | gtrid_length | bqual_length | data |
+----------+--------------+--------------+------+
|        1 |            1 |            0 | a    |
+----------+--------------+--------------+------+
1 row in set (0.00 sec)

(root@localhost) [test]> xa rollback 'a'; -- 回滾
Query OK, 0 rows affected (0.01 sec)

(root@localhost) [test]> xa recover;  -- 再看下,沒了
Empty set (0.00 sec)

這是再單實例上模擬的,意義不大

真正應用程序中兩個實例作分佈式事務,須要兩邊的prepare都成功才能最終提交

3.2 分佈式事務的不完美

  • client退出致使prepare成功事務丟失
  • MySQL Server宕機致使binlog丟失
  • 外部XA prepare成功不寫日誌

Ⅳ、事務編程

4.1 很差的事務習慣

  • 在循環中提交事務,(fsync次數太多)
  • 使用自動提交
  • 使用自動回滾
create procedure load1(count int unsigned)
begin
declare s int unsigned default 1;
declare c char(80) default repeat('a',80);
  while s <= count do
    insert into t1 select NULL,c;
    set s = s+1;
  end while;
end

call load1(1000)

上面這個存儲過程的調用,auto commit致使了insert會處罰一千次fsync

正確姿式:

begin;
call load1(1000)
commit;
  • 錯誤的那種若是中間失敗回滾都回不了,作不到原子性
  • 將事務寫到存儲過程裏面也很差,出錯了就很差弄,不能自動回滾,因此存儲過程只寫邏輯,事務控制應用程序來作

4.2 大事務

事務拆大爲小,緣由就是binlog在搞鬼,其實不必定是大事務,大的操做都要拆吧

計算利息,拆了批量執行

update account
set account_total = account_total + (1 + interest_rate)

爲何要拆?老生常談的、

  • 寫binlog成本大,致使主從延遲
  • 避免過大的undo

題外話:

binlog是有點討厭不像oracle用redo,歷史緣由,很差說

也有好處,作大數據平臺集成很是簡單,把MySQL的的數據實時推到大數據平臺上太簡單,github上一搜一大把項目直接用

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息