業(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)

其中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)思路

總結(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)單,投入成本較低