業(yè)務(wù)需求
公司的一個(gè)非高并發(fā)項(xiàng)目中提出了關(guān)于訂單號生成的規(guī)則:
業(yè)務(wù)類型(1位) + 城市編碼(6位) + 渠道(4位) + 年份(4位) + 8位遞增序列號
一共23位
并且需要滿足后8位序列號隨著年份而從1開始循環(huán)切換,即xxxx201900000001...xxxx201900000008 到
xxxx202000000001...xxxx202000000008類似的年切的規(guī)則
技術(shù)分析
基于訂單號生成本身的要求即需要在分布式環(huán)境下保證唯一性授舟,本來想利用redis單線程原子操作來實(shí)現(xiàn)唬复,但是不太好實(shí)現(xiàn)年切的需求候学,因此采用了MySQL樂觀鎖來實(shí)現(xiàn)
樂觀鎖與悲觀鎖
介紹Mysql樂觀鎖之前先簡單說一下樂觀鎖和悲觀鎖的區(qū)別
- 悲觀鎖
可以理解為悲觀的看待世事悉尾,先假定一定會發(fā)生沖突主儡,每個(gè)線程修改數(shù)據(jù)前都會先獲取鎖暑诸,從而保證同一時(shí)刻只有一個(gè)線程能操作數(shù)據(jù)逗威,從而保證數(shù)據(jù)的完整性峰搪,像Java的Synchronized就可以理解為一種悲觀鎖
- 樂觀鎖
樂觀的看待世事,每次操作數(shù)據(jù)前都會假定不會有其他人在操作修改數(shù)據(jù)凯旭,等到自己操作完準(zhǔn)備提交更新的時(shí)候判斷一下在此期間是否有其他人已經(jīng)更新了這個(gè)數(shù)據(jù)罢艾,如果沒有人更新過,就直接更新成功尽纽,如果有人更新過咐蚯,就嘗試重新獲取數(shù)據(jù),再操作弄贿,再提交更新春锋,如此循環(huán)
像Java中的atomic類就是一種樂觀鎖的實(shí)現(xiàn),通過CAS實(shí)現(xiàn)(比較并交換)
鎖分類 | 舉例 | 應(yīng)用場景 |
---|---|---|
悲觀鎖 | Synchronized | 并發(fā)寫操作多的場景 |
樂觀鎖 | atomic | 讀多寫少的場景 |
Mysql 樂觀鎖實(shí)現(xiàn)
采用增加字段的形式差凹,如version, timestamp字段
更新時(shí)將version值 + 1期奔, 將timestamp值更新侧馅,并比較該數(shù)據(jù)在數(shù)據(jù)庫中的值是否與自己取出時(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)我們開啟一個(gè)事務(wù),并執(zhí)行update語句時(shí)肺孤,會鎖定當(dāng)前update條件下的數(shù)據(jù)罗晕,直到commit事務(wù),才會釋放掉這個(gè)隱士鎖
- 顯示指定鎖
直接利用sql添加鎖
select ... where id = 1 for update
會直接鎖定該條件下的數(shù)據(jù)赠堵,其他事務(wù)再執(zhí)行該條件下數(shù)據(jù)的update操作小渊,只能等以上事務(wù)提交后,釋放鎖以后才行
訂單號生成具體實(shí)現(xiàn)
表結(jié)構(gòu)
其中sequence_key
column 字段為年切循環(huán)條件茫叭,如'2019'酬屉,'2020'
sequence_id
column 字段表示當(dāng)前年切循環(huán)條件下的自增id值
主要sql語句
// 初始化當(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};
// 樂觀鎖更新當(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é)
樂觀鎖的存在并不適合有大量并發(fā)寫的場景揍愁,如果有很多人同時(shí)下單呐萨,并發(fā)更新自增id,就會存在性能問題
其次遞歸方法如果很深莽囤,在性能方面也會存在一定問題
因此谬擦,以上實(shí)現(xiàn)只適合用于并發(fā)量不大的情況,但目前根據(jù)我們的業(yè)務(wù)場景來說時(shí)足夠了烁登,且代碼實(shí)現(xiàn)起來較為簡單,投入成本較低