1,資料庫實現
2,快取實現原理
資料庫的行級x鎖。
優點 不需要引入第三方應用。
缺點 死鎖
對資料庫效能影響,可能較長時間占用資料庫連線資源
如果業務是分庫分表的,可能支援不了
示例**
3,zookeeper實現原理
通過setnx是否成功。
當且僅當 key 不存在,將 key 的值設為 value ,並返回1;若給定的 key 已經存在,則 setnx 不做任何動作,並返回0
優點 使用廣泛
缺點 當執行緒獲得鎖,測試應用掛了,那麼這個鎖只能等到超時自動刪除。所以一定要充分考慮業務可接受的超時時間。
實現
核心**
加鎖 `string ret = jedis.set(lockname, val, "nx", "ex", this.getsessionseconds());`
解鎖 long ret = jedis.del(lockname);
延伸原理
zk的臨時順序節點。加鎖操作是客戶端去/lock目錄下建立臨時順序節點。客戶端檢查自己的節點序列號是目錄下最小的,則獲取鎖成功。否則件事比自己小的最大節點。等待獲取鎖。
優點 穩定高效。不存在鎖無法釋放的問題。即使宕機,zk會自己刪除臨時節點。
缺點 要接入zk,實現較複雜的客戶端邏輯。--但是,使用curator後也很簡單了。
實現
1,使用原生zookeeper api。步驟:建立根目錄,獲取鎖,建立臨時順序節點,排序,檢查自己是不是當前最小的znode,是則獲取鎖,不是則監聽比自己小的節點。釋放鎖,刪除節點。
2,使用apache的curator包,步驟:引入包curator-recipes。加鎖,interprocessmutex.acquire。解鎖,interprocessmutex.release();
簡單的令人髮指。
1,主鍵與索引
聚簇索引。innodb的資料組織形式。完整的記錄都儲存在主鍵索引中,通過主鍵索引可以找到記錄所有的列。
普通索引。存的是主鍵索引。即通過普通索引,找到主鍵,再通過主鍵索引可以找到記錄所有的列。
innodb對於主鍵使用了聚簇索引,這是一種資料儲存方式,表資料是和主鍵一起儲存,主鍵索引的葉結點儲存行資料。對於普通索引,其葉子節點儲存的是主鍵值。
2,sql執行計畫
3,mysql/innodb 加鎖機制分析
mysql是乙個支援外掛程式式儲存引擎的資料庫系統。
大體上可以將mysql分為兩部分:mysql server + 儲存引擎(如myisam、innodb)
以下內容基於innodb儲存引擎。
innodb基於多版本併發控制mvcc,與mvcc相對的是基於鎖的併發控制。
mvcc的最大好處是讀不加鎖,讀寫不衝突。這樣在讀多寫少的應用中,極大的提高了併發效能。
mvcc中讀又分為快照讀和當前讀。快照度顧名思義是讀的快照版本,有可能是歷史版本,不需要加鎖。當前讀是讀取的當前版本,當前讀返回的記錄需要加鎖,以保證其他事務不會併發修改。
那麼什時候是當前讀,什麼時候是快照讀?
快照讀:簡單的select語句。
select * from table where ..
不加鎖
當前讀:特殊的select語句。
select * from table where .. for update
x鎖(排它鎖)
select * from table where .. lock in share mode
s鎖(共享鎖)
增刪改操作。
insert into table values (…)
x鎖
update table set .. where ..
x鎖
delete from table where ..
x鎖
舉個例子,對於update操作,
一句sql給到mysql,mysql server解析,根據where條件,讀取第一條滿足的記錄,然後innodb將第一條記錄返回,並加鎖。
mysql server 執行update,然後innodb引擎執行update操作,並返回成功。
這樣一條記錄執行完成。
接著,進行第二條記錄的執行。
也就是說一句sql,會被拆成很多步操作。一條記錄一條記錄的加鎖,執行。
資料庫死鎖問題涉及 索引 執行順序 事務隔離級別三者。
兩階段鎖
rdbms對於加鎖的乙個原則是,兩階段鎖,即2pl。含義是,加鎖和解鎖分為兩個完全不相干的階段,加鎖時只加鎖,解鎖時只解鎖。
比如,乙個事務,先insert,再update,在delete。
那麼加鎖階段做,inser的加鎖,update的加鎖,delete的加鎖。
解鎖階段做,inser的解鎖,update的解鎖,delete的解鎖。
完全分開,不混雜。
隔離級別
rdbms四種隔離級別。
ru,read uncommit。可以讀取其他事務未提交的操作。實際工程中不會使用的,忽略。
rc,read commit。針對當前讀,加行鎖。存在幻讀情況。
rr,read repeat。針對當前讀,加行鎖,加間隙鎖(gap鎖)。所以不存在幻讀情況。
serializable。從mvcc退回到基於鎖的併發控制。讀加共享鎖,寫加排它鎖,且讀寫衝突。併發效能急劇下降,不建議使用。
幻讀:舉個例子就明白。在乙個事務中,有兩次一樣的select操作。但是第一次select之後,有其他事務做了insert操作,導致第二次查詢出的記錄多了一條。這就是幻讀。所以rr隔離加了間隙鎖,其他事務的insert操作,不能成功,也就不存在幻讀情況了。
加鎖分析
給出一條sql語句,不給出前提條件就分析是很業餘的表現。
必須要考慮哪些前提呢?
1,隔離級別
2,是不是主鍵
3,如果不是主鍵,是不是二級索引
4,如果是二級索引,它又是不是唯一索引
5,sql的執行計畫是什麼,
以一條sql為例。
update from table set .. where id = 100
1,id是主鍵,rc。 聚簇索引表id對應的這條記錄加x鎖。
2,id不是主鍵,但是二級唯一索引。rc。 id索引表id對應的這條索引記錄加x鎖。聚簇索引表對應的這條記錄加x鎖。
3,id不是主鍵,但是二級索引。rc。id索引表id對應的這條索引記錄加x鎖。聚簇索引表對應的這條記錄加x鎖。 區別就是非唯一索引,可能有多條加鎖。
幻讀就是在這種情況下發生的。
4,id上無索引。 此時只能走聚簇索引,做全表掃瞄。所有記錄全加x鎖。實際上mysql會做一層優化,mysql server會檢查將不滿足的記錄釋放掉鎖。但是這個優化違背了2pl原則。
5,id是主鍵,rr。同rc一樣。
6,id不是主鍵,但是二級唯一索引。rr。同rc一樣。
7,id不是主鍵,但是二級索引。rr。同rc的區別就是再加間隙鎖。
8,id上無索引。rr。和rc的區別是,再加上全表記錄間的間隙鎖。當然mysql也有優化。
9,serializable隔離級別。測試所有的讀都加共享鎖。
死鎖的場景
1,一種最常見的場景是,兩個事務兩條sql分別持有對方需要的行鎖。
這種情況大部分是由於加鎖順序導致,排除方法是分析事務的加鎖順序,調整加鎖順序一致。
2,另一種是兩個事務一條sql也會死鎖。
這是因為索引的問題。我們已經知道資料庫執行是一條一條執行的。
索引是單獨存在的儲存結構。
所以一條sql也是先給索引表加鎖,再給對應的聚簇索引記錄加鎖。這就會產生和第一種情況類似的場景。
因為索引是有順序的,比如name和age上都有索引。
update table set .. where name in (..);
update table set .. where age>10;
這樣兩條sql同時執行,name對應的第一條聚簇索引被鎖住,age>10對應的聚簇索引第一條也被鎖住。
然後再去鎖各自對應的第二條時,發現都被對方鎖住了。產生死鎖。
所以,死鎖歸根結底就是加鎖順序的問題。這不只是資料庫是這樣的,死鎖都是這個原因,資源的共享和占用時,需要同時持有多個資源,加鎖順序不同一定會導致死鎖。
分布式鎖 使用Redis實現分布式鎖
關於分布式鎖的實現,我的前一篇文章講解了如何使用zookeeper實現分布式鎖。關於分布式鎖的背景此處不再做贅述,我們直接討論下如何使用redis實現分布式鎖。關於redis,筆主不打算做長篇大論的介紹,只介紹下redis優秀的特性。支援豐富的資料型別,如string list map set zs...
分布式 分布式鎖
本質是利用redis的setnx 方法的特性來加鎖,setnx 即key不存在則設定key,否則直接返回false,要求在分布式系統中使用同乙個redis服務,以下提供兩種解決方案 1 直接使用redistemplate 這其實並不能完全保證高併發下的安全問題,因為可能在鎖過期之後該執行緒尚未執行完...
分布式鎖 哨兵模式 分布式鎖實現原理
背景 記錄對分布式鎖的相關理解,不斷提公升自己 可重入鎖 為什麼不建議使用redis分布鎖 主從切換可能丟失鎖資訊 考慮一下這樣的場景 在分布式環境中,很多併發需要鎖來同步,當使用redis分布式鎖,通用的做法是使用redis的setnx key value px 這樣的命令,設定乙個字段,當設定成...