淺談 SQL 中的鎖(七)如何生成自定義的自增 ID
在 SQL 表設計中,自增 ID 的使用很廣泛。因為有些資料的屬性並不具有唯一性,所以要給它加上一個生成的主鍵。生成主鍵最方便的方式,就是採用 SQL 產品提供的自增 ID 功能。可能自增 ID 的使用太過方便了,現在大有氾濫的趨勢,甚至有資深的工程師說:所有的表都應該有一個自增的主鍵。
不過 SQL 產品的自增 ID 功能,一般都只使用簡單的自增整型,就是第一行記錄的 ID 是 1,第二行記錄的 ID 是 2,如此類推。有時候我們會希望 ID 帶有除序號之外額外的資訊,比如希望使用者的 ID 帶有其部門簡寫:人事部的員工以 HR 做字首;資訊部的員工以 IT 做字首:
123456789 | create table app_user ( id char (5) primary key , dept char (2), number int ) insert app_user values ( 'HR001' , 'HR' , 1) insert app_user values ( 'IT001' , 'IT' , 1) |
面對這樣的需求,一般的處理邏輯是:先找出對應部門的最大 ID,把這個 ID 的序號部分加 1,作為新使用者的 ID 新增到使用者表中:
123456789101112 | declare @id char (5) declare @dept char (2) declare @number int set @dept = 'HR' select @number = max (number) + 1 from app_user where dept = @dept set @id = @dept + right ( '00' + convert ( varchar (3), @number), 3) insert app_user values (@id, @dept, @number) |
很明顯,上面的程式碼在併發的時候會出現問題,因為計算新的 ID 和插入記錄是兩個獨立的事務,在這兩個事務之間,新的 ID 可能會被佔用了。加入延時的程式碼,在兩個連線中執行同樣的語句:
123456789101112131415 | declare @id char (5) declare @dept char (2) declare @number int set @dept = 'HR' select @number = max (number) + 1 from app_user where dept = @dept set @id = @dept + right ( '00' + convert ( varchar (3), @number), 3) --延長處理時間 waitfor delay '0:00:10' insert app_user values (@id, @dept, @number) |
可以看到後執行的連線插入記錄失敗:
原因就是 ID 已經被先執行的連線佔用了。
實際上,解決這個問題方法,和之前寫的重複使用者問題的解決方法是一樣的:
關鍵就是在生成 ID 的時候加上範圍鎖:
123456789101112131415161718192021222324 | declare @id char (5) declare @dept char (2) declare @number int set @dept = 'HR' --開始事務 begin transaction --設定序列式事務 set transaction isolation level serializable select @number = max (number) + 1 from app_user(updlock) where dept = @dept set @id = @dept + right (
|