解決數據庫高併發出現的數據問題

談到數據庫不得不提到事務的問題,事務具備4個特性ACID,可是在數據高併發的狀況下可能會出現髒讀 、不可重複讀 、幻讀 這幾類問題。mysql

1.髒讀:sql

髒讀就是指當一個事務正在訪問數據,而且對數據進行了修改,而這種修改尚未提交到數據庫中,這時,另一個事務也訪問這個數據,而後使用了這個數據。數據庫

2.不可重複讀:併發

是指在一個事務內,屢次讀同一數據。在這個事務尚未結束時,另一個事務也訪問該同一數據。那麼,在第一個事務中的兩次讀數據之間,因爲第二個事務的修改,那麼第一個事務兩次讀到的的數據多是不同的。這樣就發生了在一個事務內兩次讀到的數據是不同的,所以稱爲是不可重複讀。(即不能讀到相同的數據內容)oracle

例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,做者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重複。若是隻有在做者所有完成編寫後編輯人員才能夠讀取文檔,則能夠避免該問題。高併發

3.幻讀:性能

是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的所有數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,之後就會發生操做第一個事務的用戶發現表中還有沒有修改的數據行,就好象spa

發生了幻覺同樣。.net

例如,一個編輯人員更改做者提交的文檔,但當生產部門將其更改內容合併到該文檔的主複本時,發現做者已將未編輯的新材料添加到該文檔中。若是在編輯人員和生產部門完成對原始文檔的處理以前,任何人都不能將新材料添加到文檔中,則能夠避免該問題。事務

數據庫爲了防止出現以上問題提出了隔離級別的概念:由低到高依次爲Read uncommitted 、Read committed 、Repeatable read 、Serializable 

√: 可能出現    ×: 不會出現

  髒讀 不可重複讀 幻讀
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × × ×

首先,咱們來舉例理解下以上四種隔離級別。

Read uncommitted 讀未提交

公司發工資了,領導把5000元打到singo的帳號上,可是該事務並未提交,而singo正好去查看帳戶,發現工資已經到帳,是5000元整,很是高 興。但是不幸的是,領導發現發給singo的工資金額不對,是2000元,因而迅速回滾了事務,修改金額後,將事務提交,最後singo實際的工資只有 2000元,singo空歡喜一場。

出現上述狀況,即咱們所說的髒讀 ,兩個併發的事務,「事務A:領導給singo發工資」、「事務B:singo查詢工資帳戶」,事務B讀取了事務A還沒有提交的數據。

當隔離級別設置爲Read uncommitted 時,就可能出現髒讀,如何避免髒讀,請看下一個隔離級別。

Read committed 讀提交

singo拿着工資卡去消費,系統讀取到卡里確實有2000元,而此時她的老婆也正好在網上轉帳,把singo工資卡的2000元轉到另外一帳戶,並在 singo以前提交了事務,當singo扣款時,系統檢查到singo的工資卡已經沒有錢,扣款失敗,singo十分納悶,明明卡里有錢,爲 何......

出現上述狀況,即咱們所說的不可重複讀 ,兩個併發的事務,「事務A:singo消費」、「事務B:singo的老婆網上轉帳」,事務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。

當隔離級別設置爲Read committed 時,避免了髒讀,可是可能會形成不可重複讀。

大多數數據庫的默認級別就是Read committed,好比Sql Server , Oracle。如何解決不可重複讀這一問題,請看下一個隔離級別。

Repeatable read 重複讀

當隔離級別設置爲Repeatable read 時,能夠避免不可重複讀。當singo拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),singo的老婆就不可能對該記錄進行修改,也就是singo的老婆不能在此時轉帳。

雖然Repeatable read避免了不可重複讀,但還有可能出現幻讀 。

singo的老婆工做在銀行部門,她時常經過銀行內部系統查看singo的信用卡消費記錄。有一天,她正在查詢到singo當月信用卡的總消費金額 (select sum(amount) from transaction where month = 本月)爲80元,而singo此時正好在外面胡吃海塞後在收銀臺買單,消費1000元,即新增了一條1000元的消費記錄(insert transaction ... ),並提交了事務,隨後singo的老婆將singo當月信用卡消費的明細打印到A4紙上,卻發現消費總額爲1080元,singo的老婆很詫異,覺得出 現了幻覺,幻讀就這樣產生了。

注:MySQL的默認隔離級別就是Repeatable read。

Serializable 序列化

Serializable 是最高的事務隔離級別,同時代價也花費最高,性能很低,通常不多使用,在該級別下,事務順序執行,不只能夠避免髒讀、不可重複讀,還避免了幻像讀。

介紹完數據庫和事務以及致使的問題,接下來咱們討論下項目中通常如何解決數據庫高併發的問題。基本上用到最多的方式就是採用樂觀鎖和悲觀鎖來解決併發問題,相比之下樂觀鎖用到的比較多一點,由於悲觀鎖比較影響數據庫性能。通常狀況在讀操做比較頻繁的狀況使用樂觀鎖比較好一點,在寫操做比較頻繁的狀況下才會使用悲觀鎖。接下來咱們腦補一下什麼是樂觀鎖和悲觀鎖:

悲觀鎖:鎖如其名,他對世界是悲觀的,他認爲別人訪問正在改變的數據的機率是很高的,因此從數據開始更改時就將數據鎖住,知道更改完成才釋放。

樂觀鎖:他對世界比較樂觀,認爲別人訪問正在改變的數據的機率是很低的,因此直到修改完成準備提交所作的的修改到數據庫的時候纔會將數據鎖住。完成更改後釋放。

咱們繼續討論怎麼解決高併發的問題,由於咱們的項目度比較多一點,因此採用的是樂觀鎖的方式。

舉個簡單的例子有一張員工信息表:hr_user_info,表結構以下所示

id,version,name,contract_start_time,contract_end_time,state

如今的業務場景是這樣的,因爲員工的合同日期即將到期,公司的HR須要在系統中將員工的合同開始日期作調整,可是有2位HR同時對一個員工進行合同信息修改,首先他們要根據姓名查詢到這個員工,而後再修改合同信息,SQL以下:

1.select id from hr_user_info where name='張三';

2.update hr_user_info set contract_start_time='2017-08-07 00:00:00',contract_end_time='2020-08-06 00:00:00' where id='0001';

若是2位HR同時查詢到這條數據而後同時去改的話,那就有可能會致使前面修改的合同別後面的覆蓋,致使數據問題,因此咱們進行一下修改來解決這個問題

1.select id,version from hr_user_info where name='張三';

2.update hr_user_info set contract_start_time='2017-08-07 00:00:00',contract_end_time='2020-08-06 00:00:00',version=version+1 where id='0001' and version=version;

這樣就能夠避免修改併發的問題了。

悲觀鎖方式的處理方式是

1.select id from hr_user_info where name='張三' for update;

2.update contract_start_time='2017-08-07 00:00:00',contract_end_time='2020-08-06 00:00:00' where id='0001';

以上是參考相關資料整理的處理併發的方法,有什麼不對的地方請大神指正。

相關文章
相關標籤/搜索