前言
接下來討論一下修改冪等性的第一個方案
數(shù)據(jù)庫冪等性
假設(shè)我們有一個 user 表, 每次有人注冊就向其中插入一條記錄, 我們要保證修改的冪等性, 初步的想法可以有二種: 第一種是先在數(shù)據(jù)庫里查詢一下, 如果沒有這個 email, 就像數(shù)據(jù)庫里插入一條, 第二種是先刪除這個email的記錄然后插入一條新的email;
我們先假設(shè)查詢(刪除)操作和插入操作不在同一個事務(wù)中, 會出現(xiàn)一種情況大概如下:
刪除也是類似的情況, 所以單純的先查(刪)后插的方式不能夠解決這個問題.
這時候呢, 可能有的小伙伴會發(fā)現(xiàn), 這個可能是由于查詢(刪除)操作和插入不在同一個事務(wù)里導(dǎo)致的, 如果在一個事務(wù)里是不是能夠解決這個問題呢.
這就涉及到事務(wù)隔離級別了, 其中分為讀未提交, 讀已提交, 可重復(fù)讀和可序列化, 講述這四種隔離級別的文章的挺多了, 其中可序列化能解決上面的問題但是性能和并發(fā)度不高, 另外應(yīng)用程序強(qiáng)依賴數(shù)據(jù)庫隔離級別太脆弱了.
樂觀鎖方案
為了兼顧性能和安全我們可以在建表的時候增加唯一鍵:
createuniqueindexuser_email_uindexonuser(email);
情況就會變成下面這樣:
比如有些場景沒有郵箱這樣明顯的唯一鍵, 可以自己手動生成一個, 比如用戶打開表單頁面就生成一個 uid 返回給前端, 前端重復(fù)點(diǎn)擊不會導(dǎo)致重提, 還可以根據(jù)統(tǒng)一的時間戳或者版本號來防止并發(fā)的修改導(dǎo)致的非預(yù)期的效果
但是這種方案也存在缺點(diǎn)
對數(shù)據(jù)庫有壓力, 尤其是沖突可能性過大會導(dǎo)致大量的事務(wù)回滾, 性能壓力就更大, 其實有點(diǎn)像定義一個方法, 輸入一個字符串, 判斷是否是整數(shù), 可以直接調(diào)用轉(zhuǎn)換方法, 拋出異常就返回不是整數(shù), java 規(guī)范中都不建議這么操作, 因為 trycatch 代價太高, 最好是先判斷一下是否符合正則等, 判斷是不是符合一般的情況, 然后再做轉(zhuǎn)換.
場景有限, 需要所需的數(shù)據(jù)庫支持樂觀鎖方式, 有些操作或者一些非關(guān)系型數(shù)據(jù)庫不能支持樂觀鎖, 這種方式就沒法使用
業(yè)務(wù)復(fù)雜起來樂觀鎖考慮的場景就更復(fù)雜, ABA 問題等, 比如上面的例子一瞬間, 第一次點(diǎn)擊的事務(wù)已經(jīng)注冊上去了, 又注銷了, 第二次點(diǎn)擊產(chǎn)生的事務(wù)又注冊上了,,, 或者業(yè)務(wù)邏輯很復(fù)雜, 不單單是插入一張表, 還需要發(fā)一封郵件, 還需要增加角色配置等, 樂觀鎖的使用場景就要考慮很多問題, 不然會導(dǎo)致多發(fā)郵件等等問題
聊完了樂觀鎖的缺點(diǎn), 我們后面的文章講述下其他方式來保證冪等性盡可能的消除樂觀鎖帶來的問題.