MySQL中RR隔離等級轉換成RC隔離等級案例

來源:互聯網
上載者:User

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

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.