1. 程式人生 > >IT修煉手冊之SQL事務

IT修煉手冊之SQL事務

事務
基本概念: MySQL 事務主要用於處理操作量大,複雜度高的資料。比如說,在資訊管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的資訊,如信箱,文章等等,這樣,一系列的資料庫的操作就構成了事務!
(1)在 MySQL 中只有使用了 Innodb 資料庫引擎的資料庫或表才支援事務。
(2)事務處理可以用來維護資料庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行。
(3) 事務用來管理 insert,update,delete 語句
(4)預設事務是自動提交的,改為手動提交的方式是:
start transaction;
update t_user set psw=11 where id =1; Commit;

事務的 4 個特性:ACID
原子性(Atomicity)、一致性(Consistency)、隔離 性(Isolation)、永續性(Durability)以銀行匯款為例,張三給李四轉款 300 元.
(1)、原子性: 是指某幾句 sql 的影響,要麼都發生,要麼都不發生.
即:張三減 300, 李四+300 , insert 銀行流水, 這 3 個操作,必須都完成,或都不產 生效果.
(2)、 一致性: 事務前後的資料,保持業務上的合理一致.
(匯款前)張三的餘額+李四的餘額 ====== (匯款後) 張三的餘額+李四餘額
比如: 張三隻有 280 元, 280-300=-20,儲蓄卡不是信用卡,不能為負,因此張三餘 0 元. 將導致, 匯款後,2 者餘額,匯款前,差了 20 元.
(3)、隔離性: 在事務進行過程中, 其他事務,看不到此事務的任何效果.

(4)、永續性: 事務一旦發生,不能取消. 只能通過補償性事務,來抵消效果. 事務與引擎:

注意點

(1)myisam 引擎不支援事務, innodb 和 BDB 引擎支援. 因此我們的實驗用 innodb 表來做;
(2)開啟事務 start transaction;
(3)執行查詢 xxxx
(4)提交事務/回滾事務. commit / rollback

併發事務處理帶來的問題
(1)更新丟失:更新的資料被別的事務所覆蓋
(2)髒讀:一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的 資料處於有不一致的狀態;這時另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了 這些‘髒’資料,並據此做進一步的處理就會產生未提交的資料依賴關係。稱之為髒讀。
事務 A 讀取到了事務 B 已修改但是未提交的資料,並在這個資料基礎上做了操作。如果 B 回滾,A 讀取的資料無效,不符合一致性要求。
(3)不可重複讀

:一個事務在讀取某些資料後的某個時間,再次讀取以前讀過的資料,卻發現獨處的資料已經發生了改變,或者某些記錄以及被刪除了這種現象稱之為 ‘不可重複讀’
事務 A 讀取到了事務 B 已經提交的修改資料,不符合隔離型。
(4)幻讀:一個事務按照相同的查詢條件重新讀取以前檢索過的資料,卻發現其他事務插入了滿足查詢條件的新資料,這種現象稱之為‘幻讀’;
事務 A 讀取到了事務 B 提交的新資料,不符合隔離性。幻讀與髒讀比較類似: 髒讀是事務 B 裡面修改了資料
幻讀是事務 B 裡面新增了資料。

檢視資料庫隔離級別
show variables like ‘%iso%’;

設定隔離級別
set tx_isolation=’READ-UNCOMMITTED’

隔離級別
read uncommitted:讀未提交的事務內容,顯然不符原子性,稱為”髒讀”. 在業務中,沒人這麼用.
read commited: 在一個事務進行過程中, 讀不到另一個進行事務的操作,但是,可以讀到另一個結束事務的操作影響。即當第一個事務結束時候,第二個事務仍然在進行中的時候可以讀到第一個結束事務產生的資料。故當一個沒結束的事務操作不能夠外界所看到,並且一個沒結束的事務也不能看到外界的變化。
repeatable read: 可重複讀,即在一個事務過程中,所有資訊都來自事務開始那一瞬間的資訊, 不受其他已提交事務的影響. (大多數的系統,用此隔離級別)。
事務鎖級別擴充套件:
1、如兩個事務 A 和 B;當 A 事務對某一行資料做寫操作時,B 資料先與 A 也在對同一行資料做寫操作,那麼 A 事務將被掛起,等待 B 事務 commit 之後執行。這是針對 innodb 引擎來講的,如果是 myisam 的話如果 A 對某個表的某個記錄做寫操作,那麼 B 對該表的任何行做操作都會被掛起,因為 myisam 鎖的是整個表。
2、如果 AB 兩個事務同時對同一行資料做操作,A 事務對資料做寫操作並且提交,等 A 事務提交完之後 B 事務在對改行做寫操作會依賴與 A 事務的結果,但是 B 事務做讀操作不依賴於 A 的結果。
(4)serializeable 序列化, 所有的事務,必須編號,按順序一個一個來執行,也就取消了衝突的可能. 這樣隔離級別最高,但事務相互等待的等待長. 在實用,也不是很多。
這裡寫圖片描述
預設是可重複讀的級別


鎖的劃分
鎖分為:行鎖、表鎖和頁鎖

按照資料的操作來分:lock tables table_name read(write) unlock tables
讀鎖:針對同一份資料,多個讀操作可以同時進行而不會相互形象,也叫共享鎖
寫鎖:當前寫操作沒有完成之前,他會阻斷其他寫鎖和讀鎖。也叫排他鎖
按照操作粒度來分:行鎖、表鎖

表鎖
對於一些非事務性行的表(如:myisam )操作可以採用鎖的方式來處理,常用的兩類,讀鎖定和寫鎖定;讀鎖定是指所有的回話只能讀取資料不能寫資料,寫鎖定是當前加鎖的回話可 以讀寫,但是其他的回話不能讀也不能寫。
這裡寫圖片描述
在sesion1 中為表加寫鎖
Sesion1 為表加寫鎖 Sesion2
當前sesion 對錶增刪查都可執行 其他sesion 對鎖表查詢被阻塞,需要等待釋放鎖
釋放鎖 session2 獲得鎖完成查詢/插入/更新操作
這裡寫圖片描述
簡而言之,就是讀鎖會阻塞寫、但是不會阻塞讀。而寫鎖會把讀和寫都阻塞。

行鎖
這裡寫圖片描述
使用 innodb,提交模式改為手動提交
Sesion1 Sesion2

更新id 為 4 的記錄,沒有手寫commit 更新id 為 4 的記錄,阻塞只能等待(id 為非 4 不
影響)
更新提交 接觸阻塞,正常更新

更新id 為 4 的記錄,沒有手寫commit select id 為 4 的記錄,查詢不到 session 的更新值
如果想鎖定一行的話:
begin
Select * from test where id =4 for update
//在沒有 commit 之前如果別的 session 更新這行記錄會被阻塞。

行鎖升級為表鎖
如果在 update 的時候 where 後面的欄位如果沒加索引或者索引失效的話,行鎖會升級成表鎖,大幅度降低系統性能。

間隙鎖
當我們用範圍條件而不是相等條件檢索資料,並請求共享或排他鎖時,InnoDB 會給符合條件的已有資料記錄的索引項加鎖;對於建值在條件範圍內但並不存在的記錄叫做間隙
(GAP),INNODB 也會對這個間隙加鎖,這種所機制就是間隙鎖。

因為 Query 在執行過程中通過範圍查詢的話,他會鎖定整個範圍內所有索引建值,即使這個建值並不存在。間隙鎖有一個比較致命的弱點,就是當鎖定一個範圍建值之後,即使 某些不存在的建值也會被無辜的鎖定,而造成鎖定的時候無法插入鎖定建值範圍內的任何數 據。在某些場景下可能會對效能造成很大的危害。

悲觀鎖
悲觀鎖體現的場景是當我們開啟一個事務或者是讓提交模式程式設計手動提交的時候才會體現出來,如果是自動提交的話表示這每一個 sql 都會是一個事務,當 sql 執行完也就意味著鎖釋放,感覺不出鎖的存在。對於悲觀鎖主要應用的是資料庫的鎖機制即為排他鎖(寫鎖),當加上該所之後根據不同的資料庫引擎表現出來的不一樣,如果是 innodb 的話體現出來的是鎖中該行,也就是說在當前事務沒有 commit 之前,其他的事務讀寫都要等待。如果是
mysiam 的話其他的事務如果對該表進行操作的時候要等待,範圍比 innodb 要大。

悲觀鎖的使用釋放是在 select 的時候在後面加上 for update 表示把這一行給鎖住了,可以保證外界對該行的資料毫無干擾,保證了資料的完整性。當該執行緒對這行資料寫操作完之後,提交事務。其他因為也要去鎖改行而被掛起的事務在進行 select 的時候會獲取最新的資料。
樂觀鎖
樂觀鎖是由應用程式提供的一種機制, 通過版本控制和時間戳的方式來實現。比如往資料庫中的某個表更新一條記錄時,首先 SELECT 出來一條記錄(v0.1),這是開啟兩個事務 A 和 B 對其進行更新操作,假設 A 事務對其進行了更新並提交了事務,那麼資料庫中的資料發生了變化,於此同時版本也變成 v0.2;此時B 事務跟新提交時,如果叫上版本校驗的話會出現錯誤,因為資料庫中的版本比B 事務要新,所以要先SELECT 一次是版本變成和資料庫同步,在進行更新操作。
對於實際應用中實現版本控制,可以在資料庫中加一個普通的欄位為 version.每次更新或者其他操作時檢查 version 欄位的內容和本次查找出來的內容是否一樣,一樣執行不一樣再次查詢後執行。
Eg:Mybatis 對樂觀所得應用(在原由的實體類中加入 version 欄位屬性值,並在資料庫中加入version 這個clumn):
這裡寫圖片描述
UPDATE T_USER u SET u.address = #address#, u.version = u.version + 1
WHERE u.username = #username# AND u.version = #version#
UPDATE 會返回一個更新結果的行數,如果更新執行返回的數量是 0 表示產生併發修改了, 需要重新獲得最新的資料後再進行更新操作。
本節完後續更新