應用開發中,我們經常需要涉及到數據主鍵的生成。大部分情況,我們會采用數據庫主鍵自增,比如學生表,讓學生表里的id自增。但是如果我們希望主鍵里保護日期信息呢?或者我們在庫里實行了分表策略,表主鍵自增也是不行的。
有人會想到uuid,uuid能做到全局唯一的,能解決分表策略的問題,當時在主鍵里加入其他信息還是不行。還有個問題uuid字符串比較長,存儲費空間,不方便建索引。
我之前采用的一個策略是建立一個專門的主鍵表,里面包含兩個字段(id, tablename) .
sql如下:
CREATE TABLE `id_temp` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `table_name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `table_name` (`table_name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
生成id:
REPLACE INTO id_temp (table_name) value("tablename") ;
SELECT LAST_INSERT_ID() ;
如果我們業務表不介意id的連續性,可以多個業務表使用一個主鍵表。如果業務表要求id的連續性,那一個業務表得使用一個主鍵表。
如果需要主鍵中加入日期元素,需要使用當前日期加上上面方式生成的id就成了。
但是,當業務並發很大,這種生成速度已經滿足不了需求時,怎么辦呢?
以前支付寶紅包剛出來的時候,記得發紅包的人,生成紅包后,會產生一個數字,其他人通過這個數字搶紅包。假設某個關鍵時間點,大量用戶在那個時間點生成紅包,那這些數字怎么生成呢(這些數字必須得有唯一性)。
記得之前在一篇博客介紹過一個方案,地址忘記了,介紹的是Fickr使用的一種主鍵生成方案。他的方案和上面的類似,不過是一個分布式版。
選擇N個數據庫服務器,每台數據庫建一個主鍵表,也是兩個字段(id, tablename)。
不同的是,第一台數據庫的建表語句是:
CREATE TABLE `id_temp` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `table_name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `table_name` (`table_name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
第m台建表語句是:
CREATE TABLE `id_temp` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `table_name` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `table_name` (`table_name`) ) ENGINE=InnoDB AUTO_INCREMENT=m DEFAULT CHARSET=utf8;
m是 1 到 N中間的一個數字,每台數據庫主鍵的初始值不同,從1到N。
然后,每台數據庫設置他們的 auto_increment_increment值,
set @@session.auto_increment_increment=N ;
大概意思我們應該明白了,1到N台服務器,每台服務器的自增初始值不同,分別從1到N,然后每次自增的長度是N。 應用服務器分布式調用到不同的數據庫后,都能保證id的唯一性,和單台數據庫的效果是一樣的,而且使用上數據庫集群,解決了高並發的問題。
這里在引申一下,假如需要添加服務器,或者下線服務器,怎么辦呢?
方案一:
我們可以假設在這個上線、下線數據庫改動期間,id能自增到的最大值M(通過平時的統計,可以有更多的富余),取一個稍微大於M的一個值,記作K。然后每台數據庫,重新設置他的 auto_increment_increment和auto_increment_offset值。第一台auto_increment_offset值設為K,第二台設為K+1,依次第m台設為K+m;每台的auto_increment_increment值設置為新的服務器數。
方案二:
先生成足夠的id在緩存中,后面的請求不走db,全部從緩存取。然后修改每台數據庫的auto_increment_increment和auto_increment_offset值,參數修改完后,在恢復從db取。
