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層級的可重複讀特性。
問題現象
下面來看另一個不正常的情況,環境是和上面一致的,沒有改變,我們來直接看圖:
可以看到,執行方式和上面一致,右邊的事務等待了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模式,再試一遍。
好了。一切正常了,這就是RR的特性,可重複讀。
本文永久更新連結地址:https://www.bkjia.com/Linux/2018-03/151339.htm