MySQL的INSERT ... FOR UPDATE你用過嗎
引言
了不起最近處理了一個(gè)生產(chǎn)問題,具體情況是這樣的:
就是兩個(gè)sql語句在同一個(gè)代碼塊中,第一個(gè)sql語句是對(duì)設(shè)備狀態(tài)根據(jù)設(shè)備ID批量刪除,第二個(gè)sql語句是批量插入,插入的設(shè)備ID和刪除的設(shè)備ID是同一個(gè)。
本地和測(cè)試環(huán)境當(dāng)時(shí)都好好的,可是上了生產(chǎn)環(huán)境,報(bào)錯(cuò)了

錯(cuò)誤是存在重復(fù)的唯一索引,代碼邏輯很簡(jiǎn)單

可以看到就是一個(gè)批量刪除,再批量插入,測(cè)試環(huán)境當(dāng)時(shí)沒有用多個(gè)實(shí)例進(jìn)行測(cè)試,上產(chǎn)是有多個(gè)實(shí)例的,錯(cuò)誤的發(fā)生情況當(dāng)天下班后一直分析到晚上12點(diǎn),終于分析出來了,如下圖:

可以看到是同時(shí)來了兩個(gè)請(qǐng)求,分別落到了2臺(tái)機(jī)器,這段代碼在2個(gè)機(jī)器上執(zhí)行就不能保證順序性了,這個(gè)要加鎖的話也是分布式鎖。
不過還可以通過數(shù)據(jù)庫的行鎖來實(shí)現(xiàn),接下來我們就一起來看看INSERT ... FOR UPDATE語句。
MySQL的?INSERT ... FOR UPDATE
語句可以用于在插入新行的同時(shí)對(duì)這些行進(jìn)行加鎖,從而防止其他事務(wù)對(duì)這些行進(jìn)行修改。下面簡(jiǎn)要介紹?INSERT ... FOR UPDATE
語句的原理和應(yīng)用場(chǎng)景,并舉例說明其具體用法。
一、INSERT ... FOR UPDATE
語句的原理
INSERT ... FOR UPDATE
語句會(huì)在執(zhí)行插入操作時(shí),對(duì)插入的新行進(jìn)行排他鎖(Exclusive Lock)的加鎖操作,從而避免其他事務(wù)對(duì)這些新行進(jìn)行修改。
這種鎖定機(jī)制同樣是基于MySQL的事務(wù)隔離級(jí)別實(shí)現(xiàn)的,只有在可重復(fù)讀(REPEATABLE READ)和串行化(SERIALIZABLE)隔離級(jí)別下,INSERT ... FOR UPDATE
語句才能生效。
二、INSERT ... FOR UPDATE
語句的應(yīng)用場(chǎng)景
防止重復(fù)插入
在某些業(yè)務(wù)場(chǎng)景下,可能需要對(duì)某些數(shù)據(jù)進(jìn)行唯一性約束,避免重復(fù)插入相同的數(shù)據(jù)。使用?INSERT ... FOR UPDATE
語句可以避免并發(fā)的情況下,多個(gè)事務(wù)同時(shí)插入相同的數(shù)據(jù)。
實(shí)現(xiàn)悲觀鎖
在某些業(yè)務(wù)場(chǎng)景下,需要對(duì)某些數(shù)據(jù)進(jìn)行嚴(yán)格的控制,確保數(shù)據(jù)不會(huì)被其他事務(wù)修改。使用?INSERT ... FOR UPDATE
語句可以實(shí)現(xiàn)悲觀鎖,確保數(shù)據(jù)的安全性。
實(shí)現(xiàn)樂觀鎖
使用?INSERT ... FOR UPDATE
語句可以實(shí)現(xiàn)樂觀鎖機(jī)制,即在插入數(shù)據(jù)的同時(shí),記錄數(shù)據(jù)的版本號(hào)或時(shí)間戳等信息,如果在插入時(shí)發(fā)現(xiàn)版本號(hào)或時(shí)間戳已經(jīng)存在,則說明其他事務(wù)已經(jīng)插入了相同的數(shù)據(jù),需要進(jìn)行相應(yīng)的處理。
三、INSERT ... FOR UPDATE
語句的應(yīng)用舉例
下面舉例說明?INSERT ... FOR UPDATE
語句的具體用法。
假設(shè)有一個(gè)用戶表user,其中包含用戶ID和用戶名兩個(gè)字段,現(xiàn)在需要往用戶表中插入新用戶,假設(shè)插入流程如下:
根據(jù)用戶ID查詢用戶是否已經(jīng)存在
如果用戶不存在,則插入新用戶
在這個(gè)過程中,需要對(duì)用戶ID進(jìn)行唯一性約束,避免重復(fù)插入相同的用戶。
可以使用如下的SQL語句來實(shí)現(xiàn)插入操作:
START TRANSACTION;SELECT * FROM user WHERE user_id = '123' FOR UPDATE;INSERT INTO user(user_id, username) VALUES ('123', 'John');COMMIT;
在這個(gè)SQL語句中,SELECT ... FOR UPDATE
語句用于對(duì)選定的行進(jìn)行加鎖,防止其他事務(wù)對(duì)這些行進(jìn)行修改,從而避免重復(fù)插入相同的用戶。如果查詢結(jié)果為空,則可以插入。
四、生產(chǎn)問題的解決
了不起這邊處理的方式就很簡(jiǎn)單了,直接將批量插入sql的語句末尾加上了 FOR UPDATE。
語法參考:
insert into tablename values() on duplicate key update columnName = valueName;
由于具體涉及公司隱私就不貼sql語句了,原理就是當(dāng)執(zhí)行插入的時(shí)候,如果已經(jīng)有這條數(shù)據(jù)存在,那么就更新它,就不會(huì)存在說已經(jīng)插入了的數(shù)據(jù)在插入一條會(huì)報(bào)重復(fù)Key這個(gè)情況。
簡(jiǎn)單的INSERT ... FOR UPDATE你掌握了吧,后面我們繼續(xù)學(xué)習(xí)。