目录
1. 使用 auto_increment_increment 和 auto_increment_offset
对数据库表进行水平分割的时候,经常会遇到在不同的机器上生成全局唯一 ID 的问题。下面以 MySQL 为例介绍对于在不同机器上生成全局唯一 ID 的几个解决途径,其思想也适用于其他数据库系统。
1. 使用 auto_increment_increment 和 auto_increment_offset
假定有 n 个数据库,它们使用了 auto_increment 字段来取得唯一 ID,为了保证 n 个数据库同时具备全局唯一性,我们可以在每个数据库上分别指定初始值和步长,具体如下:
- 对于任一节点 i(1<=i<=n)指定:
auto_increment_increment=i auto_increment_offset=n
- 如果以后系统扩展到 m 个数据库(m>n),则对于任一节点 i(1<=i<=m)指定:
auto_increment_increment=全局max(id)+i auto_increment_offset=m
这种方法很简单,而且也不依赖于一个中心节点,因此是生成唯一 ID 的上佳选择。现有的服务器也很容易用这个方法来配置,特别是当你增加服务器或灾难恢复之后。
2. 创建全局 ID 表
在一个全局数据库节点上创建一个带有 auto_increment 字段的表,应用就从这个表取得唯一性 ID。
-- 建立myisam全局表
create table single_row
(
col1 int not null auto_increment,
col2 int not null
primary key(col1),
unique key(col2)
) engine=myisam;
-- 用replace让表里的数据保持一条
replace into single_row (col2) values (1);
在这个语句执行后,可以使用 MySQL 的 API:mysql_insert_id() 来获得这个新生成的值。这个方法在不同语言中的实现都不太一样,以下是一个 PERL 的例子:
my $sth = $dbh -> prepare('replace into single_row(col2) values (1)');
while (my $item = @work_to_do){
$sth->execute();
my $id = $dbh->(mysql_insert_id);
#Do the work...
}
用不着再使用另外的查询语句(比如 select last_insert_id())来获取这个值。额外的查询又会在服务器之间多一次往返,这很没效率。如果使用这种全局分配器来产生唯一 ID,要当心它会成为应用的瓶颈。
3. 使用 memcache
在 memcache API 中有个 incr() 函数,它能产生一个唯一性 ID 供使用。虽然 memcache 很快(每秒钟几万个值),但它不是持续不断的。每次重启了 memcache 服务,都需要在缓存里初始化那个产生值。这就要求你初始化时,每次都要找出目前在各个数据分块使用的最大值,这个过程相当缓慢而且难以自动执行。
4. 批量分配编号
应用从全局节点上一次性地取得一批编号供自己使用,用完后,再申请一批。
5. 使用复合值
你可以使用一个复合值来做唯一性 ID,比如一个数据库 ID 和自增长编号。可以使用单列主键值,并且使用整数的“高位”来保存数据库 ID。简单的左移位(乘法)和加法可以完成这一目的。例如,使用无符号的 bigint(64位)的高 8 位来保存数据库 ID,那么就可以用下面的方式在数据库 15 上插入 11:
insert into test(pk_col,...) values ((15<<56)+11,...);
这种方法的问题是需要额外的方式来产生键值,因为 auto_increment 不会帮助你做这件事。而且这种方法使主键值更大了,对 InnoDB 中的次主键有多重影响。
6. 使用双字段 auto_increment 键
这个只能在 myisam 表里使用:
create table inc_test
(
a int nout null,
b int not null auto_increment,
primary key (a,b)
) engine=myisam;
insert into inc_test(a) values(1),(1),(2),(2);
select * from inc_test;
7. 使用 GUID
你可以使用 UUID() 函数来生成全局唯一的 ID,GUID 的值很大且不连续,因此它不适合做 InnoDB 表里的主键。MySQL 中新的 UUID_SHORT 函数能返回一个较短的连续的值,也更适于用作主键。