一個幻讀模型引出的記錄可見性判斷

Ⅰ、看一個幻讀的模型

表a中有4條記錄1,3,5,7,開兩個sessionmysql

session1:begin;    delete <= 7 ;
session2:begin;    insert a values(2);    commit;
session1:commit;

這個模型在rc的狀況下,這是沒問題的,只是加記錄鎖,不會鎖範圍,插入2是能夠的。最後a上就剩2這個記錄sql

那binlog裏面記錄的內容就有講究了,假設是statment格式的binlog數據庫

insert 2;
delete <= 7;

這時候數據庫作同步,從庫上就gg了,一個事務所作的修改對另外一個事務不可見,好似串行session

這就是不符合隔離性的要求,並行執行和串行執行的結果不同併發

row格式的binlog是下面這樣記錄的:ide

insert 2;
delete 1,delete 3, delete 5, delete 7

這樣2就還在性能

因此,必定要用row,row記錄的是一條一條記錄,而不是簡單的sql.因此說rc的狀況下設置爲row,主從仍是能夠保持一致的測試

5.1版本才支持row,主從複製從3.23就開始支持,中間差了4.0,4.1,5.0三個版本,最先爲何innodb要支持這樣的鎖,也有部分緣由是當時的MySQL的複製不用Next-key Lock,就是根本不可用的,由於innodb能夠並行的,有併發問題,不像其餘存儲引擎有表鎖優化

tips:code

  • binlog_format這個參數5.7以前默認statement,以後默認row
  • 其實不用咱們擔憂,在事務隔離級別爲rc的狀況下,binlog_format隨便你怎麼設MySQL都會給你記爲row的,不信能夠試試

Ⅱ、如何判斷一條記錄的可見性

先講兩句題外話,這個判斷的是根據事務id來作的,每條記錄有rowid、txid、rowpointer,後面才接用戶的列

txid是6個字節的,其實每一個事務分配的,並且是全局自增的,在共享表空間的某個位置存放着當前max的txid,因此每開啓一個事務都會分配一個txid

2.1 具體怎麼判斷?

①當前活躍事務列表,裏面記錄着當前正在執行未提交的事務

begin;
xxx    隨便什麼語句

這時候產生一個read_view的內存對象,如今在mysql裏面徹底看不到,能看到的就是下面這個,有多少個read_view開着,咱們經過這個read_view來判斷記錄的可見性

--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=23137, Main thread ID=139830599059200, state: sleeping
Number of rows inserted 116, updated 27, deleted 0, read 130
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s

這個read_view就是把事務開始的時候事務活躍列表拷貝一份出來,這時候會拿到不少個事務id,只要你的記錄對應的事務id在這個活躍列表中,意味着這個記錄不可見,由於這條記錄在select這個事務開啓的時候還沒提交,因此該記錄的事務id對新產生的事務是不可見的

②read_view在rc和rr中的區別
rr中read_view只產生一次,rc中每執行一條sql語句就會建立一個read_view

緣由:rc能夠讀到已經提交的事務的記錄,第二次執行還要檢查事務活躍列表,若是提交了這條記錄就可見,而rr實現了可重複讀

若是一個事務執行時間很長,它插入了一條記錄,是否可見?就看建立read_view的時候這個tx_id有沒有在活躍列表中,若是不在就意味着不可見,那就不會刪掉了。

rr的好處,read_view只建立一次,rc要建立n次

若是事務活躍列表很長的話,每次拷貝的時候要鎖住全部活躍事務(latch,5.5版本叫kernel_mutex,這個鎖很大),須要時間仍是挺長的,但5.6,5.7都開始作優化了(把大鎖拆分了)

因此,一個事務中有不少條操做,並且全是select操做(隨機主鍵值查詢),這時候rr性能更好,沒有insert因此不會有gap鎖致使的併發插入影響,這種狀況太少了,因此咱們仍是選擇rc

2.2 read_view何時分配?

begin的時候仍是執行第一條sql的時候?

測一把便知

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

mysql> desc l;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| a     | int(11) | NO   | PRI | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)

mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
+---+
3 rows in set (0.00 sec)

測試一:

session1:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

session2:
mysql> begin;                 
Query OK, 0 rows affected (0.00 sec)

mysql> insert into l values (4);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
+---+
4 rows in set (0.00 sec)

session1:
mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
+---+
4 rows in set (0.00 sec)

測試二:

session1:
mysql>
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
+---+
3 rows in set (0.00 sec)

session2:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into l values (4);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
+---+
4 rows in set (0.00 sec)

session1:
mysql> select * from l;
+---+
| a |
+---+
| 1 |
| 2 |
| 3 |
+---+
3 rows in set (0.00 sec)

按道理若是事務隔離級別爲rr,那一個事務提交了,對另外一個事務不可見,解決不可重複讀,這樣看測試二是合理的,
那爲何,測試二session1一開始select了一把,session2裏面事務提交了,session1就不可見,而測試一session1一開始沒有select,後面再select就可見了?這是重複讀的體現嗎?

緣由:rr隔離級別的時,事務中有select時會建立一個read_view,並且一個事務只建立一次,因此測試一的時候,最後session1 select的時候才建立了read_view,發現session2的事務中相關記錄已經commit了,不在事務活躍列表中,因此讀到了這條記錄,而測試二,session1 開啓事務,第一個select的時候就建立了read_view,這時候session2裏面的事務還沒開啓,第二個select的時候用的仍是原來的rv,這樣就不可見了

tips:
若是但願begin的時候就建立read_view

必須用start transaction with consistent snapshot; 結合rr用,由於rc時候,事務中每執行一個sql就會建立read_view

session1:
start transaction with consistent snapshot;    建立了rv

session2:
begin;
insert aaa;
commit;

session1:
select aaa;查不到,建立rv的時候,session2中的事務還不存在

若是rc的話能讀到aaa,由於第三步session1裏執行select又會建立一個rv,會發現aaa這個記錄已經提交了,就能看到了
相關文章
相關標籤/搜索