mysql數據庫分表之後重點在於新聞之間的關系了,對於小編找了到了一篇關於數據庫分表後生成全局id方法,希望對各位有幫助。
最近一個項目由於數據量變大,需要進行數據分表。數據存儲在淘寶的tddl上。分表後,原先的自增id就不能使用了。tddl對java支持很好,分表後無需考慮全局id的問題。但是這個項目使用的是php進行開發,必須自己生成全局id。以下列出幾種分表方案,僅當拋磚引玉。
方法1:使用CAS(compare and swap)
其實這裡並不是嚴格的CAS,而是使用了比較交換原子操作的思想。
生成思路如下:
每次生成全局id時,先從sequence表中獲取當前的全局最大id。然後在獲取的全局id上做加1操作。把加1後的值更新到數據庫。更新時是關鍵。
如加1後的值為203,表名是users,數據表結構如下:
代碼如下
復制代碼
CREATE TABLE `SEQUENCE` (
`name` varchar(30) NOT NULL COMMENT '分表的表名',
`gid` bigint(20) NOT NULL COMMENT '最大全局id',
PRIMARY KEY (`name`)
) ENGINE=InnoDB
那麼更新語句是。
代碼如下
復制代碼
update sequence set gid = 203 where name = ‘users’ and gid < 203;
sql語句的 and gid < 203 是為了保證並發環境下gid的值只增不減。
如果update語句的影響記錄條數為0說明,已經有其他進程提前生成了203這個值,並寫入了數據庫。需要重復以上步驟從新生成。
代碼實現如下:
代碼如下
復制代碼
//$name 表名
function next_id_db($name){
//獲取數據庫全局sequence對象
$seq_dao = Wk_Sequence_Dao_Sequence::getInstance();
$threshold = 100; //最大嘗試次數
for($i = 0; $i < $threshold; $i++){
$last_id = $seq_dao->get_seq_id($name);//從數據庫獲取全局id
$id = $last_id +1;
$ret = $seq_dao->set_seq_id($name, $id);
if($ret){
return $id;
break;
}
}
return false;
}
方案2:使用全局鎖。
在進行並發編程時,一般都會使用鎖機制。其實,全局id的生成也是解決並發問題。
生成思路如下:
在使用redis的setnx方法和memcace的add方法時,如果指定的key已經存在,則返回false。利用這個特性,實現全局鎖。
每次生成全局id前,先檢測指定的key是否存在。
如果不存在則使用redis的incr方法或者memcache的increment進行加1操作。這兩個方法的返回值是加1後的值。
如果存在,則程序進入循環等待狀態。循環過程中不斷檢測key是否還存在,如果key不存在就執行上面的操作。
代碼如下:
代碼如下
復制代碼
//使用redis實現
//$name 為 邏輯表名
function next_id_redis($name){
$redis = Wk_Redis_Util::getRedis();//獲取redis對象
$seq_dao = Wk_Sequence_Dao_Sequence::getInstance();//獲取存儲全局id數據表對象
if(!is_object($redis)){
throw new Exception("fail to create redis object");
}
while(1){
//檢測key是否存在,相當於檢測鎖是否存在
$ret = $redis->setnx("sequence_{$name}_flag",time());
if($ret){
break;
}
$time = $redis->get("sequence_{$name}_flag");
if(time() - $time > 1){//如果循環等待時間大於1秒,則不再等待。
break;
}
}
$id = $redis->incr("sequence_{$name}");
//如果操作失敗,則從sequence表中獲取全局id並加載到redis
if (intval($id) === 1 or $id === false) {
$last_id = $seq_dao->get_seq_id($name);//從數據庫獲取全局id
if(!is_numeric($last_id)){
throw new Exception("fail to get id from db");
}
$ret = $redis->set("sequence_{$name}",$last_id);
if($ret == false){
throw new Exception("fail to set redis key [ sequence_{$name} ]");
}
$id = $redis->incr("sequence_{$name}");
if(!is_numeric($id)){
throw new Exception("fail to incr redis key [ sequence_{$name} ]");
}
}
$seq_dao->set_seq_id($name, $id);//把生成的全局id寫入數據表sequence
$redis->delete("sequence_{$name}_flag");//刪除key,相當於釋放鎖
$db = null;
return $id;
}
方案3:redis和db結合。
使用redis直接操作內存,可能性能會好些。但是如果redis死掉後,如何處理呢?把以上兩種方案結合,提供更好的穩定性。
代碼如下:
代碼如下
復制代碼
function next_id($name){
try{
return $this->next_id_redis($name);
}
catch(Exception $e){
return $this->next_id_db($name);
}
}
另外對於全局id的生成,Flicker和Twitter也都公布了自己的方案。感興趣的人,可以了解下