如果沒有鎖,那麼並發性會更強,但是數據安全性會有問題。因此數據庫會給數據加鎖。
也就是讀寫鎖,共享鎖可以疊加共享鎖但是不能加排他鎖, 排他鎖則不能疊加。 根據隔離級別等等,mysql會隱式的給數據自動加鎖 此外還可以使用share in model, for update 等語句顯示的加鎖
粒度越細,維護鎖的開銷越大,並發性越高,數據越不安全。 通常有如下兩種
- 行鎖
只給一行數據加鎖
- 表鎖
給整張表加鎖, 一般Alter table會使用表鎖
mysql為了提高沖突監測性能而存在的一種鎖。是給其上一級所加的鎖,所以在mysql中通常為表鎖。
比如一個事務,給兩行加了排他鎖,又有一個事務想要給整個表加共享鎖,這個時候就要去查看所有的表的行是否有鎖策略不能加共享鎖。 如果有了意向鎖,那麼事務1就能再給兩行加排他鎖的同時,給整個表加IX, 這樣事務2不用遍歷就能知道不可以加S鎖了。
A,B兩個人同時在系統上進行操作,比如要修改一份訂單。 當A正在修改准備提交前向上廁所,這兒時候B修改完訂單並且提交了。 這個時候A回來了再提交就會出現問題,B的修改被覆蓋了。
- 悲觀鎖
for update, share in model語句
使用悲觀鎖,則會在操作期間全程對數據進行加鎖,其他人不能再次修改。這樣會造成比較長時間的阻塞。
適用於短事務,寫量比較大的情況。
- 樂觀鎖
樂觀鎖通過在數據庫中新增一個version字段,操作後給version + 1. 提交時比較,如果比數據庫中的大,則提交。大概的代碼是:
if(connection.update("update set name='123' where id=1 and version < #current_version#") > 0){
// 表示更新成功了
}
這種適用於讀多,寫少,並且寫有可能會用很長時間的情況。
死鎖
比如有兩個事物:
事物1
update table1 set name='1' where id=1;
// sleep 1
update table1 set name='2' where id=2;
commit;
事物2
update table1 set name='2' where id=2;
// sleep 2
update table1 set name='1' where id=1;
commit;
上面的語句如果恰巧一起執行到sleep1 和sleep2,那麼就會造成死鎖。 一般有三種做法解決:
一次鎖所有數據 保持鎖的順序 允許死鎖,然後kill掉代價最小的事務。回滾之
間隙鎖
執行select .. from where id between這樣的語句的時候鎖定這一區間,不能插入或者刪除數據,以防止幻讀。
MVCC 多版本並發控制
mysql維護一個系統版本號,每次有新的事物開始的時候遞增。
每行後面保存兩個隱藏列。 一個創建時版本號C,一個刪除時版本號D
INSERT 新增的行往C裡寫入當前系統版本號。 這樣新事物可以通過這個版本號保證不查到他 DELETE 為刪除的行寫D字段為當前系統版本號。 UPDATE 插入一行新數據寫C為當前系統版本號,老數據寫D為當前系統版本號。 SELECT 的時候只查 C<=當前版本 && (D > 當前版本 || D is not defined) 這樣主要是為了在不加鎖的情況下,一個事務能夠讀取到事務開始前已經存在且未被刪除,且沒有經過修改的數據。 也就是解決髒讀的問題。即該事物開始之後的其他事務的各種修改事務提交都不會對當前事務的讀取產生影響。同時也解決了幻讀的問題
語句與鎖
InnoDB 的行鎖是基於索引的,如果沒有索引,或者不能使用索引則是表鎖 select … from 一致性非阻塞讀,不上鎖。 select … from where lock in share mode 共享鎖 select … from where … for update 排他鎖 update .. where 排他鎖 delete … from 排他鎖
下面的例子來看鎖的排他性
事務1
set autocommit=0;
begin;
SELECT * FROM biz_pay_task where id = 1 FOR UPDATE;
// wait
commit;
事務2
set autocommit=0;
begin;
SELECT * FROM biz_pay_task where id = 1 LOCK IN SHARE MODE;
commit;
事務2在執行SELECT LOCK IN SHARE MODE的時候會阻塞,知道事務1commit之後才會完成。