記一個mysql環境RR隔離級別轉換成RC的問題

先了解RR(REPEATABLE-READ)和RC(READ-COMMITTED)的區別.

RR隔離級別增加了間隙鎖,避免了幻讀,並且阻止了不可重複讀,讓同一個事務裏面的查詢和修改都是一致的.mysql默認的隔離級別就是RR.

雖然說RC隔離級別在同一個事務內會存在查詢出不同數據的現象,但是這些數據都必然是提交過的,是真實存進硬盤的數據.所以也不用過分擔憂,而且RC隔離級別反而降低了鎖粒度,也不是毫無用處.oracle和sql server默認的隔離級別類似RC.

所以說也不是說RC就絕對不好,要看場景來選擇,而這裏只是簡介,不打算深入.

操作流程說明:因系統高併發下,存在多個會話可能同時更新同一條記錄的問題,但是值是一樣的.問題就在於事務裏面存在RR隔離級別轉換成RC的問題,造成數據返回不正確,導致代碼返回錯誤,但是數據是準確的.


正常的RR事務

先看當前環境信息:

#當前的mysql版本
mysql> select @@version;
+------------+
| @@version  |
+------------+
| 5.6.39-log |
+------------+
1 row in set (0.00 sec)
#當前的隔離級別
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
#當前的binlog格式
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | MIXED |
+---------------+-------+
1 row in set (0.00 sec)

先看一個正常的事務:

#開啓事務

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

#開啓事務

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

#當前記錄是一致的

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         2000 |
+--------------+
1 row in set (0.02 sec)

#當前記錄是一致的

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         2000 |
+--------------+
1 row in set (0.02 sec)

#這邊先更新一條記錄

mysql> update m_order_sub set express_cost = 3000 where order_sub_no = 'O152022324482662671828';

Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0


#這邊後更新一條記錄,但是另一邊並沒有commit,所以這邊處於等待釋放鎖

update m_order_sub set express_cost = 3000 where order_sub_no = 'O152022324482662671828';

#這邊再查詢一次,記錄成功修改

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         3000 |
+--------------+
1 row in set (0.00 sec)

#提交事務

mysql> commit;

Query OK, 0 rows affected (0.01 sec)

#然後鎖釋放後這邊的更新也執行完了,但是因爲更新的值是一樣的,所以並沒有修改到記錄,Changed爲0

Query OK, 0 rows affected (12.40 sec)

Rows matched: 1  Changed: 0  Warnings: 0

#這邊再查詢一次,記錄成功修改,是最新數據

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         3000 |
+--------------+
1 row in set (0.00 sec)

#這邊查詢結果是舊的,因爲記錄並沒有被修改到,所以顯示的也是事務開始時的數據

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         2000 |
+--------------+
1 row in set (0.00 sec)

#提交併退出事務

mysql> commit;

Query OK, 0 rows affected (0.13 sec)

#這時就顯示最新的數據了

mysql> select express_cost from m_order_sub where order_sub_no = 'O152022324482662671828';

+--------------+
| express_cost |
+--------------+
|         3000 |
+--------------+
1 row in set (0.00 sec)

這是一個正常的情況,因爲記錄並沒有被修改到,所以顯示的也是事務開始時的數據,保證了RR級別的可重複讀特性.


問題現象

下面來看另一個不正常的情況,環境是和上面一致的,沒有改變,我們來直接看圖:

1.png

可以看到,執行方式和上面一致,右邊的事務等待了12秒後執行了,也就是左邊commit之後.但是,變成了不可重複讀,右邊事務裏面沒有commit也可以看到最新提交的數據,甚是詭異.


解決方法

第一種方案:將隔離級別改成RC貌似是可以解決問題,但是解決的是左邊的問題,把可重複讀的特性改成了不可重複讀了而已.這樣兩邊都能查到已經提交的新數據.

#更改mysql全局隔離級別爲RC
set global tx_isolation = 'READ-COMMITTED'

改了之後,全局都變成了不可重複讀,並且沒有了間隙鎖,也正因爲可以看到已經提交的新數據,所以上面正常的情況也會跟下面一致,但是不代表有問題,這本身就是RC隔離級別的特點.

然後有人說,這不是沒解決問題嘛,只是把問題全部改成一樣而已,好像是這樣.所以就有第二種方案.


第二種方案:把binlog格式改成ROW,不用改隔離級別,問題是真的解決了.

#把全局binlog格式改成ROW格式
set global binlog_format = 'ROW';

在上面看到原始的的binlog格式是MIXED混合模式,現在改成ROW模式,再試一遍.

2.png

好了,一切正常了,這就是RR的特性,可重複讀.










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章