一次利用Mysql樂(lè)觀鎖實(shí)現(xiàn)的訂單號(hào)自增實(shí)現(xiàn)方案

業(yè)務(wù)需求

公司的一個(gè)非高并發(fā)項(xiàng)目中提出了關(guān)于訂單號(hào)生成的規(guī)則:
業(yè)務(wù)類型(1位) + 城市編碼(6位) + 渠道(4位) + 年份(4位) + 8位遞增序列號(hào)
一共23位

并且需要滿足后8位序列號(hào)隨著年份而從1開(kāi)始循環(huán)切換,即xxxx201900000001...xxxx201900000008 到
xxxx202000000001...xxxx202000000008類似的年切的規(guī)則

技術(shù)分析

基于訂單號(hào)生成本身的要求即需要在分布式環(huán)境下保證唯一性,本來(lái)想利用redis單線程原子操作來(lái)實(shí)現(xiàn),但是不太好實(shí)現(xiàn)年切的需求,因此采用了MySQL樂(lè)觀鎖來(lái)實(shí)現(xiàn)

樂(lè)觀鎖與悲觀鎖

介紹Mysql樂(lè)觀鎖之前先簡(jiǎn)單說(shuō)一下樂(lè)觀鎖和悲觀鎖的區(qū)別

  • 悲觀鎖

可以理解為悲觀的看待世事,先假定一定會(huì)發(fā)生沖突,每個(gè)線程修改數(shù)據(jù)前都會(huì)先獲取鎖,從而保證同一時(shí)刻只有一個(gè)線程能操作數(shù)據(jù),從而保證數(shù)據(jù)的完整性,像Java的Synchronized就可以理解為一種悲觀鎖

  • 樂(lè)觀鎖

樂(lè)觀的看待世事,每次操作數(shù)據(jù)前都會(huì)假定不會(huì)有其他人在操作修改數(shù)據(jù),等到自己操作完準(zhǔn)備提交更新的時(shí)候判斷一下在此期間是否有其他人已經(jīng)更新了這個(gè)數(shù)據(jù),如果沒(méi)有人更新過(guò),就直接更新成功,如果有人更新過(guò),就嘗試重新獲取數(shù)據(jù),再操作,再提交更新,如此循環(huán)

像Java中的atomic類就是一種樂(lè)觀鎖的實(shí)現(xiàn),通過(guò)CAS實(shí)現(xiàn)(比較并交換)

鎖分類 舉例 應(yīng)用場(chǎng)景
悲觀鎖 Synchronized 并發(fā)寫操作多的場(chǎng)景
樂(lè)觀鎖 atomic 讀多寫少的場(chǎng)景

Mysql 樂(lè)觀鎖實(shí)現(xiàn)

采用增加字段的形式,如version, timestamp字段

更新時(shí)將version值 + 1, 將timestamp值更新,并比較該數(shù)據(jù)在數(shù)據(jù)庫(kù)中的值是否與自己取出時(shí)的一致,一致則更新成功,否則就表示已經(jīng)有別人更新了

sql

update table_name set num = #{num}, version = version + 1 where id = 1 and version = #{version}

Mysql 顯示和隱士鎖

我們知道m(xù)ysql的InnoDB采用的是類似兩階段鎖定協(xié)議,即存在顯示鎖定和隱士鎖定兩種

  • 隱士鎖

當(dāng)我們開(kāi)啟一個(gè)事務(wù),并執(zhí)行update語(yǔ)句時(shí),會(huì)鎖定當(dāng)前update條件下的數(shù)據(jù),直到commit事務(wù),才會(huì)釋放掉這個(gè)隱士鎖

  • 顯示指定鎖

直接利用sql添加鎖

select ... where id = 1 for update

會(huì)直接鎖定該條件下的數(shù)據(jù),其他事務(wù)再執(zhí)行該條件下數(shù)據(jù)的update操作,只能等以上事務(wù)提交后,釋放鎖以后才行


訂單號(hào)生成具體實(shí)現(xiàn)

表結(jié)構(gòu)

用于年切自增序列號(hào)維護(hù)表

其中sequence_key column 字段為年切循環(huán)條件,如'2019','2020'
sequence_id column 字段表示當(dāng)前年切循環(huán)條件下的自增id值

主要sql語(yǔ)句

// 初始化當(dāng)前年切條件自增id數(shù)據(jù)
insert ignore into order_sequence (sequence_id, sequence_key) values(1, #{key});

// 獲取當(dāng)前年切循環(huán)條件下的自增id值
select sequence_id from order_sequence where sequence_key = #{key};

// 樂(lè)觀鎖更新當(dāng)前年切循環(huán)條件下的id + 1
update order_sequence set sequence_id = sequence_id + 1 where sequence_key = #{key} and sequence_id = #{sequenceId};

Java代碼實(shí)現(xiàn)

具體編碼細(xì)節(jié)不作展示,這里給出實(shí)現(xiàn)思路

訂單號(hào)自增id生成流程

總結(jié)

樂(lè)觀鎖的存在并不適合有大量并發(fā)寫的場(chǎng)景,如果有很多人同時(shí)下單,并發(fā)更新自增id,就會(huì)存在性能問(wèn)題

其次遞歸方法如果很深,在性能方面也會(huì)存在一定問(wèn)題

因此,以上實(shí)現(xiàn)只適合用于并發(fā)量不大的情況,但目前根據(jù)我們的業(yè)務(wù)場(chǎng)景來(lái)說(shuō)時(shí)足夠了,且代碼實(shí)現(xiàn)起來(lái)較為簡(jiǎn)單,投入成本較低

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容