成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL 可重復(fù)讀,差點(diǎn)就讓我背上了一個(gè) P0 事故!

數(shù)據(jù)庫(kù) MySQL
為了保證資金安全,余額發(fā)生扣減時(shí),需要比較現(xiàn)有余額與扣減金額大小,若扣減金額大于現(xiàn)有余額,扣減余額不足,扣減失敗。

 小黑黑的碎碎念

哎,最近有點(diǎn)忙,備考復(fù)習(xí)不利,明天還要搬家,好難啊!!

本想著這周鴿了,但是想想還是不行,爬起來(lái),更新一下,周更可不能斷。偷懶一下,修改一下之前的一篇?dú)v史文章,重新發(fā)布一下。

ps: 發(fā)這篇文章的時(shí)候,正在打加賽,JD 加油!!

[[328851]]

P0 事故:余額多扣!

這是一個(gè)真實(shí)的生產(chǎn)事件,事件起因如下:

現(xiàn)有一個(gè)交易系統(tǒng),每次產(chǎn)生交易都會(huì)更新相應(yīng)賬戶的余額,出賬扣減余額,入賬增加余額。

為了保證資金安全,余額發(fā)生扣減時(shí),需要比較現(xiàn)有余額與扣減金額大小,若扣減金額大于現(xiàn)有余額,扣減余額不足,扣減失敗。

賬戶表(省去其他字段)結(jié)構(gòu)如下:

  1. CREATE TABLE `account` 
  2.     `id`      bigint(20) NOT NULL
  3.     `balance` bigint(20) DEFAULT NULL
  4.     PRIMARY KEY (`id`) 
  5.  
  6. ) ENGINE = InnoDB 
  7.   DEFAULT CHARSET = utf8mb4 
  8.   COLLATE = utf8mb4_bin; 

扣減余額時(shí),sql 語(yǔ)序如下所示:

更新余額 sql 語(yǔ)序

ps:看到上面的語(yǔ)序,有沒(méi)有個(gè)小問(wèn)號(hào)?為什么相同查詢了這么多次?

其實(shí)這些 SQL 語(yǔ)序并不在同個(gè)方法內(nèi),并且有些方法被抽出復(fù)用,所以導(dǎo)致一些相同查詢結(jié)果沒(méi)辦法往下傳遞,所以只得再次從數(shù)據(jù)庫(kù)中查詢。

為了防止并發(fā)更新余額,在 t3 時(shí)刻,使用寫鎖鎖住該行記錄。若加鎖成功,其他線程的若也執(zhí)行到 t3,將會(huì)被阻塞,直到前一個(gè)線程事務(wù)提交。

t5 時(shí)刻,進(jìn)入到下一個(gè)方法,再次獲取賬戶余額,然后在 Java 方法內(nèi)比較余額與扣減金額,若余額充足,在 t7 時(shí)刻執(zhí)行更新操作。

上面的 SQL 語(yǔ)序看起來(lái)沒(méi)有什么問(wèn)題吧,實(shí)際也是這樣的,賬戶系統(tǒng)已經(jīng)在生產(chǎn)運(yùn)行很久,沒(méi)出現(xiàn)什么問(wèn)題。但是這里需要說(shuō)一個(gè)前提,系統(tǒng)數(shù)據(jù)庫(kù)是 Oracle 。

但是從上面表結(jié)構(gòu),可以得知此次數(shù)據(jù)庫(kù)被切換成 MySQL,系統(tǒng)其他任何代碼以及配置都不修改(sql 存在小改動(dòng))。

就是這種情況下,并發(fā)執(zhí)行發(fā)生余額多扣,即實(shí)際余額明明小于扣減金額,但是卻做了余額更新操作,最后導(dǎo)致余額變成了負(fù)數(shù)。

下面我們來(lái)重現(xiàn)并發(fā)這種情況,假設(shè)有兩個(gè)事務(wù)正在發(fā)執(zhí)行該語(yǔ)序,執(zhí)行順序如圖所示。

注意點(diǎn):數(shù)據(jù)庫(kù)使用的是 MySQL,默認(rèn)事務(wù)隔離等級(jí),即 RR。數(shù)據(jù)庫(kù)記錄為 id=1 balance=1000,假設(shè)只有當(dāng)時(shí)只有這兩個(gè)事務(wù)在執(zhí)行。

各位讀者可以先思考一下,t2,t3,t4,t5,t6,t11 時(shí)刻余額多少。

下面貼一下事務(wù)隔離等級(jí)RR 下的答案。

事務(wù) 1 的查詢結(jié)果為:

  • t2 (1,1000)
  • t4 (1,1000)
  • t6 (1,1000)

事務(wù) 2 的查詢結(jié)果為:

  • t3 (1,1000)
  • t5 (1,900)
  • t11 (1,1000)

有沒(méi)有跟你想的結(jié)果的一樣?

接著將事務(wù)隔離等級(jí)修改成 RC,同樣再來(lái)思考一下 t2,t3,t4,t5,t6,t11 時(shí)刻余額。

再次貼下事務(wù)隔離等級(jí)RC 下的答案。

事務(wù) 1 的查詢結(jié)果為:

  • t2 (1,1000)
  • t4 (1,1000)
  • t6 (1,1000)

事務(wù) 2 的查詢結(jié)果為:

  • t3 (1,1000)
  • t5 (1,900)
  • t11 (1,900)

事務(wù) 1 的查詢結(jié)果,大家應(yīng)該會(huì)沒(méi)有什么問(wèn)題,主要疑問(wèn)點(diǎn)應(yīng)該在于事務(wù) 2,為什么換了事務(wù)隔離等級(jí)結(jié)果卻不太一樣?

下面我們先帶著疑問(wèn),了解一下 MySQL 的相關(guān)原理 ,看完你就會(huì)明白這一切。

  • MVCC
  • 一致性視圖
  • 快照讀與當(dāng)前讀

MVCC

我們先來(lái)看下一個(gè)簡(jiǎn)單的例子,

事務(wù)隔離等級(jí)為 RR , id=1 balance=1000

更新時(shí)序

事務(wù) 1 將 id=1 記錄 balance 更新為 900,接著事務(wù) 2 在 t5 時(shí)刻查詢?cè)撔杏涗浗Y(jié)果,很顯然該行記錄應(yīng)該為 id=1 balance=1000。

如果 t5 查詢最新結(jié)果 id=1 balance=900,這就讀取到事務(wù) 1 未提交的數(shù)據(jù),顯然不符合當(dāng)前事務(wù)隔離級(jí)別。

從上面例子可以看到 id=1 的記錄存在兩個(gè)版本,事務(wù) 1 版本記錄為 balance=1000 ,事務(wù) 2 版本記錄為 balance=900。

上述功能,MySQL 使用 MVCC 機(jī)制實(shí)現(xiàn)功能。

MVCC:Multiversion concurrency control,多版本并發(fā)控制。摘錄一段淘寶數(shù)據(jù)庫(kù)月報(bào)的解釋:

多版本控制: 指的是一種提高并發(fā)的技術(shù)。最早的數(shù)據(jù)庫(kù)系統(tǒng),只有讀讀之間可以并發(fā),讀寫,寫讀,寫寫都要阻塞。引入多版本之后,只有寫寫之間相互阻塞,其他三種操作都可以并行,這樣大幅度提高了 InnoDB 的并發(fā)度。在內(nèi)部實(shí)現(xiàn)中,與 Postgres 在數(shù)據(jù)行上實(shí)現(xiàn)多版本不同,InnoDB 是在 undolog 中實(shí)現(xiàn)的,通過(guò) undolog 可以找回?cái)?shù)據(jù)的歷史版本。找回的數(shù)據(jù)歷史版本可以提供給用戶讀(按照隔離級(jí)別的定義,有些讀請(qǐng)求只能看到比較老的數(shù)據(jù)版本),也可以在回滾的時(shí)候覆蓋數(shù)據(jù)頁(yè)上的數(shù)據(jù)。在 InnoDB 內(nèi)部中,會(huì)記錄一個(gè)全局的活躍讀寫事務(wù)數(shù)組,其主要用來(lái)判斷事務(wù)的可見性。

可以看到 MVCC 主要用來(lái)提高并發(fā),還可以用來(lái)讀取老版本數(shù)據(jù)。

在學(xué)習(xí) MVCC 原理之前,首先我們需要了解 MySQL 記錄結(jié)構(gòu)。

行記錄

如上圖所示,account 表一行記錄,除了真實(shí)數(shù)據(jù)之外,還會(huì)存在三個(gè)隱藏字段,用來(lái)記錄額外信息。

  • DB_TRX_ID:事務(wù) id。
  • DB_ROLL_PTR: 回滾指針,指向 undolog。
  • ROW_ID:行 id,與此次無(wú)關(guān)。

MySQL InnoDB 里面每個(gè)事務(wù)都會(huì)有一個(gè)唯一事務(wù) ID,它在事務(wù)開始的時(shí)候會(huì)跟 InnoDB 的事務(wù)系統(tǒng)申請(qǐng)的,并且嚴(yán)格按照順序遞增的。

每次事務(wù)更新數(shù)據(jù)時(shí),將會(huì)生成一個(gè)新的數(shù)據(jù)版本,然后會(huì)把當(dāng)前的事務(wù) id 賦值給當(dāng)前記錄的 DB_TRX_ID。并且數(shù)據(jù)更新記錄(1,1000---->1,900)將會(huì)記錄在 undo log(回滾日志)中,然后使用當(dāng)前記錄的 DB_ROLL_PTR 指向 und olog。

這樣 MySQL 就可以通過(guò) DB_ROLL_PTR 找到 undolog 推導(dǎo)出之前版本記錄內(nèi)容。

查找過(guò)程如下:

查找過(guò)程

若需要知道 V1 版本記錄,首先根據(jù)當(dāng)前版本 V3 的 DB_ROLL_PTR 找到 undolog,然后根據(jù) undolog 內(nèi)容,計(jì)算出上一個(gè)版本 V2。以此類推,最終找到 V1 這個(gè)版本記錄。

V1,V2 并不是物理記錄,沒(méi)有真實(shí)存在,僅僅具有邏輯意義。

一行數(shù)據(jù)記錄可能同時(shí)存在多個(gè)版本,但并不是所有記錄都能對(duì)當(dāng)前事務(wù)可見。不然上面 t5 就可能查詢到最新的數(shù)據(jù)。所以查找數(shù)據(jù)版本時(shí)候 MySQL 必須判斷數(shù)據(jù)版本是否對(duì)當(dāng)前事務(wù)可見。

一致性視圖

MySQL 會(huì)在事務(wù)開始后建立一個(gè)一致性視圖(并不是立刻建立),在這個(gè)視圖中,會(huì)保存所有活躍的事務(wù)(還未提交的事務(wù))。

假設(shè)當(dāng)前事務(wù)保存活躍事務(wù)數(shù)組為如下圖。

視圖數(shù)組

判斷版本對(duì)于當(dāng)前事務(wù)是否可見時(shí),基于以下規(guī)則判斷:

  • 若版本事務(wù) id 小于當(dāng)前活躍事務(wù) id 數(shù)組最小值,比如版本 id 為 40,小于活躍數(shù)組最小值 45。這就代表當(dāng)前版本的事務(wù)已提交,當(dāng)前版本對(duì)于當(dāng)前事務(wù)可見。
  • 若版本事務(wù) id 大于當(dāng)前活躍事務(wù)數(shù)組的最大值,如版本事務(wù) id 為 100, 大于數(shù)組最大事務(wù) id 90。說(shuō)明了這個(gè)版本是當(dāng)前事務(wù)創(chuàng)建之后生成,所以這個(gè)版本對(duì)于當(dāng)前事務(wù)不可見。
  • 若版本事務(wù) id 是當(dāng)前活躍數(shù)組事務(wù)之一,比如版本事務(wù) id 為 56。代表記錄版本所屬事務(wù)還未提交,所以該版本對(duì)于當(dāng)前事務(wù)不可見。
  • 若版本事務(wù) id 不是當(dāng)前活躍數(shù)組事務(wù)之一,但是事務(wù) id 位于活躍數(shù)組最小值與最大值之一,比如如事務(wù) ID 57。代表當(dāng)前記錄事務(wù)已提交,所以該版本對(duì)于當(dāng)前事務(wù)可見。
  • 若版本事務(wù) id 為當(dāng)前事務(wù) id,代表該行數(shù)據(jù)是當(dāng)前事務(wù)變更的,當(dāng)然得可見。

4 這個(gè)規(guī)則可能比較繞,結(jié)合上面圖片比較好理解。

以上判斷規(guī)則可能比較抽象,看不懂,沒(méi)事,我們?cè)儆么蟀自捊忉屢幌拢?/p>

  • 未提交事務(wù)生成的記錄版本,不可見。
  • 視圖生成前,已提交事務(wù)生成記錄版本可見。
  • 視圖生成后,新事務(wù)生成記錄版本不可見。
  • 自身事務(wù)更新永遠(yuǎn)可見。

一致性視圖只會(huì)在 RR 與 RC 下才會(huì)生成,對(duì)于 RR 來(lái)說(shuō),一致性視圖會(huì)在第一個(gè)查詢語(yǔ)句的時(shí)候生成。而對(duì)于 RC 來(lái)說(shuō),每個(gè)查詢語(yǔ)句都會(huì)重新生成視圖。

當(dāng)前讀與快照讀

MySQL 使用 MVCC 機(jī)制,可以讀取之前版本數(shù)據(jù)。這些舊版本記錄不會(huì)且也無(wú)法再去修改,就像快照一樣。所以我們將這種查詢稱為快照讀。

當(dāng)然并不是所有查詢都是快照讀,select .... for update/ in share mode 這類加鎖查詢只會(huì)查詢當(dāng)前記錄最新版本數(shù)據(jù)。我們將這種查詢稱為當(dāng)前讀。

問(wèn)題分析

講完原理之后,我們回過(guò)頭分析一下上面查詢結(jié)果的原因。

這里我們將上面答案再貼過(guò)來(lái)。

事務(wù)隔離級(jí)別為 RR,t2,t3 時(shí)刻兩個(gè)事務(wù)由于查詢語(yǔ)句,分別建立了一致性視圖。

t4 時(shí)刻,由于事務(wù) 1 使用 select.. for update 為 id=1 這一行上了一把鎖,然后獲取到最新結(jié)果。而 t5 時(shí)刻,由于該行已被上鎖,事務(wù) 2 必須等待事務(wù) 1 釋放鎖才能繼續(xù)執(zhí)行。

t6 時(shí)刻根據(jù)一致性視圖,不能讀取到其他事務(wù)提交的版本,所以數(shù)據(jù)沒(méi)變。t8 時(shí)刻余額扣減 100,t9 時(shí)刻提交事務(wù)。

此時(shí)最新版本記錄為 id=1 balance=900。

由于事務(wù) 1 事務(wù)已提交,行鎖被釋放,t5 成功獲取到鎖。由于 t5 是當(dāng)前讀,所以查詢的結(jié)果為最新版本數(shù)據(jù)(1,900)。

重點(diǎn)來(lái)了,當(dāng)前這條記錄的最新版本數(shù)據(jù)為 (1,900),但是最新版本事務(wù) id,卻是事務(wù) 2 創(chuàng)建之后未提交的事務(wù),位于活躍事務(wù)數(shù)組中。所以最新記錄版本對(duì)于事務(wù) 2 是不可見的。

沒(méi)辦法只能根據(jù) undolog 去讀取上一版本記錄 (1,1000) ,這個(gè)版本記錄剛好對(duì)于事務(wù) 2 可見,所以 t11 的記錄為 (1,1000)。

而當(dāng)我們將事務(wù)隔離等級(jí)修改成 RC,每次都會(huì)重新生成一致性視圖。所以 t11 時(shí)刻重新生成了一致性視圖,這時(shí)候事務(wù) 1 已提交,當(dāng)前最新版本的記錄對(duì)于事務(wù) 2 可見,所以 t11 的結(jié)果將會(huì)變?yōu)?(1,900)。

總結(jié)

MySQL 默認(rèn)事務(wù)隔離等級(jí)為 RR,每一行數(shù)據(jù)(InnoDB)的都可以有多個(gè)版本,而每個(gè)版本都有獨(dú)一的事務(wù) id。

MySQL 通過(guò)一致性視圖確保數(shù)據(jù)版本的可見性,相關(guān)規(guī)則總結(jié)如下:

對(duì)于 RR 事務(wù)隔離等級(jí),普通查詢僅能查到事務(wù)啟動(dòng)前就已經(jīng)提交完成的版本數(shù)據(jù)。

對(duì)于 RC 事務(wù)隔離等級(jí),普通查詢可以查到查詢語(yǔ)句啟動(dòng)前就已經(jīng)提交完成的版本數(shù)據(jù)。

當(dāng)前讀總是讀取最新版本的數(shù)據(jù)。

參考資料

[1]https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html

[2]http://mysql.taobao.org/monthly/2017/12/01/

[3]http://mysql.taobao.org/monthly/2018/11/04/

[4]https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

本文轉(zhuǎn)載自微信公眾號(hào)「程序通事」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系程序通事公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序通事
相關(guān)推薦

2021-04-30 07:09:48

SQLP0事故

2025-03-10 08:20:53

代碼線程池OOM

2020-07-02 08:22:56

MySQL間隙鎖過(guò)行鎖

2022-10-17 08:31:03

生產(chǎn)環(huán)境P0項(xiàng)目

2022-03-13 22:50:47

P0故障HBase

2021-06-07 10:20:31

2023-12-05 09:46:30

2024-07-16 08:19:46

MySQL數(shù)據(jù)InnoDB

2010-09-30 16:21:40

DB2隔離級(jí)別

2013-02-25 10:48:53

RubyWeb

2021-08-05 06:46:39

P0故障公司

2021-10-08 07:50:57

軟件設(shè)計(jì)程序

2021-06-11 16:59:41

MySQLRepeatableRead

2021-06-28 14:13:34

OOM內(nèi)存事故

2024-05-13 11:46:33

MySQL數(shù)據(jù)庫(kù)

2020-04-09 10:43:12

長(zhǎng)事務(wù)P0故障

2024-04-22 00:00:01

Redis集群

2021-12-28 06:55:09

事故訂單號(hào)績(jī)效

2020-08-04 08:44:08

HashCode

2025-01-17 13:38:30

支付寶P0事故
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 男女国产网站 | 国产精品成人一区二区 | 欧美日韩一区二区在线 | 伊人春色在线观看 | 欧美成人一区二免费视频软件 | 我要看免费一级毛片 | 午夜精品一区二区三区免费视频 | 网站一区二区三区 | 亚洲精品乱码久久久久久9色 | 午夜播放器在线观看 | 亚洲欧美在线视频 | 国产精品精品视频一区二区三区 | 成人免费黄视频 | 久久久.com | 亚洲欧美在线一区 | 欧美日韩三级在线观看 | 久久久久久久av | 国产午夜视频 | 亚洲视频欧美视频 | 99视频网| 国产精品中文在线 | 精品成人| 日韩精品四区 | 国产区一区二区三区 | 亚洲va欧美va天堂v国产综合 | 91精品久久久久久久久中文字幕 | 中文字幕不卡视频在线观看 | 请别相信他免费喜剧电影在线观看 | 免费激情av | 免费午夜视频 | 精品国产一区二区久久 | 不卡视频一区二区三区 | 国产xxx在线观看 | 国产综合精品一区二区三区 | 国产欧美精品一区二区色综合朱莉 | 欧美性久久 | 国产高清在线视频 | 精品久久久久久久久久久院品网 | 一区二区三区国产好的精 | 中文字幕一区二区三区日韩精品 | 亚洲精品一区在线 |