分库设计中的主键选择

标签: 设计 主键 选择 | 发表时间:2011-04-11 00:00 | 作者:(author unknown) Epile
出处:http://www.zolazhou.com/

在先前的文章《又拍网架构中的分库设计》中, 我有提到过MySQL分库设计中的主键选择问题。在这篇文章里我想对这个问题进行展开讨论, 以此作为对上一篇文章的一个补充。

前面提到又拍网采用了全局唯一的字段作为主键。比如拿照片表为例, 虽然不同用户的照片数据存放在不同的Shard(或者说MySQL节点/实例, 请参考《又拍网架构中的分库设计》)上, 但是每一张照片拥有整个站点唯一的ID作为标示。

为什么要全局唯一?

我们在对数据库集群作扩容时,为了保证负载的平衡,需要在不同的Shard之间进行数据的移动, 如果主键不唯一,我们就没办法这样随意的移动数据。起初,我们考虑采用组合主键来解决这个问题。 一般会以user_id和一个自增的photo_id来作为主键,这的确能解决移动数据可能带来的主键冲突问题, 但是就像在“又拍网架构中的分库设计”中描述的那样当Shard之间的数据发生关系后, 我们需要用更多的字段来组成主键以保证唯一性,因此主键的索引会变的很大,从而影响查询性能, 同时也会影响写入性能。

其次,每个Shard由两台MySQL服务器组成,而这两台服务器采用master-master的复制方式, 以保证每个Shard一直可写。master-master复制方式必须保证在两台服务器上各自插入的数据有不同的主键, 不然当复制到另外一台时就会出现主键重复错误。如果我们保证主键全局唯一,就自然的解决了这个问题。 在没有采用数据拆分的设计当中,如果要用自增字段,可以参考这篇文章里的解决办法。

可能的解决方案

  • UUID

或许可以采用UUID作为主键,但是UUID好长的一串,放在URL里好难看啊,有木有? 当然这个不是关键所在,更重要的原因还是性能。UUID的生成没有顺序性,所以在写入时, 需要随机更改索引的不同位置,这就需要更多的IO操作,如果索引太大而不能存放在内存中的话就更是如此。 而UUID索引时,一个key需要32个字节(当然如果采用二进制形式存储的话可以压缩到16个字节), 因此整个索引也会相对比较大。

  • MySQL自增字段

在单个MySQL数据库的应用中一般设置一个自增的字段就可以了,而在水平分库的设计当中,这种方法显然不能保证全局唯一。 那么我们可以单独建立一个库用来生成ID,在Shard中的每张表在这个ID库中都有一个对应的表,而这个对应的表只有一个字段, 这个字段是自增的。当我们需要插入新的数据,我们首先在ID库中的相应表中插入一条记录,以此得到一个新的ID, 然后将这个ID作为插入到Shard中的数据的主键。这个方法的缺点就是需要额外的插入操作,如果ID库变的很大, 性能也会随之降低。所以一定要保证ID库的数据集不要太大,一个办法是定期清理前面的记录。

  • 引入其它工具

RedisMemcached等都支持原子性的increment操作,而且因为它们的优秀性能可以减少写入时的额外开销, 也许我们可以拿它们当作序列生成器。Memcached的问题在于不持久性,所以我们不会考虑。 而Redis也不是实时持久的,当然也可以配置成实时的,但那样怪怪的。当然也有一些持久的工具, 比如Kyoto CabinetTokyo CabinetMongoDB等等,传说中性能都不错,但是引入其它工具会增加架构的复杂程度, 也会增加维护成本。我们的团队很小,精力有限,我们奉行够用就好的原则,也就是没有特别的原因, 在可以接受的情况下,尽量用我们熟悉的工具解决问题。所以,我们还是来考虑一下怎么样用MySQL来解决这个问题吧。

更好的方案

我们一开始就是采用了上面所描述的MySQL自增字段的方法, 后来看到《Ticket Servers: Distributed Unique Primary Keys on the Cheap》 这篇文章里所描述的方法,豁然开朗。我经常这样想:如果没有那些开源产品、没有那些无私分享经验的人, 光凭我们自己的能力能做到什么程度。很感谢那些人,所以我也尽量多的分享一些自己的经验。

我先描述一下Flickr那篇文章里所描述的方法,他们使用了REPLACE INTO这个MySQL的扩展功能。 REPLACE INTO和INSERT的功能一样,但是当使用REPLACE INTO插入新数据行时, 如果新插入的行的主键或唯一键(UNIQUE Key)已有的行重复时,已有的行会先被删除,然后再将新数据行插入。 你可以放心,这是原子操作。

建立类似下面的表:


CREATE TABLE `tickets64` (
    `id` bigint(20) unsigned NOT NULL auto_increment,
    `stub` char(1) NOT NULL default '',
    PRIMARY KEY  (`id`),
    UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM;

当需要获得全局唯一ID时,执行下面的SQL语句:


REPLACE INTO `tickets64` (`stub`) VALUES ('a');
SELECT LAST_INSERT_ID();

第一次执行这个语句后,ticket64表将包含以下数据:


+--------+------+
| id     | stub |
+--------+------+
| 1      |    a |
+--------+------+

以后再次执行前面的语句,stub字段值为a的行已经存在,所以MySQL会先删除这一行,再插入。 因此,第二次执行后,ticket64表还是只有一行数据,只是id字段的值为2。 这个表将一直只有一行数据。

Flickr为Photo, Group, Account, Task各自建立了一张ticket表以保持各自的ID的连续性。 其它业务表的ID都使用同一个ticket表产生。

不错吧,其实还可以更棒。比如,只需要一张ticket表就可以为所有的业务表提供各自连续的ID。 下面,来看一下我们的方法。首先来看一下表结构:


CREATE TABLE `sequence` (
    `name` varchar(50) NOT NULL,
    `id` bigint(20) unsigned NOT NULL DEFAULT '0',
    PRIMARY KEY (`name`)
) ENGINE=InnoDB;

注意区别,id字段不是自增的,也不是主键。在使用前,我们需要先插入一些初始化数据:


INSERT INTO `sequence` (`name`) VALUES 
('users'), ('photos'), ('albums'), ('comments');

接下来,我们可以通过执行下面的SQL语句来获得新的照片ID:


UPDATE `sequence` SET `id` = LAST_INSERT_ID(`id` + 1) WHERE `name` = 'photos';
SELECT LAST_INSERT_ID();

我们执行了一个更新操作,将id字段增加1,并将增加后的值传递到LAST_INSERT_ID函数, 从而指定了LAST_INSERT_ID的返回值。

实际上,我们不一定需要预先指定序列的名字。如果我们现在需要一种新的序列,我们可以直接执行下面的SQL语句:


INSERT INTO `sequence` (`name`) VALUES('new_business') ON DUPLICATE KEY UPDATE `id` = LAST_INSERT_ID(`id` + 1);
SELECT LAST_INSERT_ID();

这里,我们采用了INSERT ON DUPLICATE KEY UPDATE这个MySQL扩展, 这个扩展的功能也和INSERT一样插入一行新的记录,但是当新插入的行的主键或唯一键(UNIQUE Key)和已有的行重复时, 会对已有行进行UPDATE操作。

需要注意的是,当我们第一次执行上面的语句时,因为还没有name为new_business的字段,所以正常的执行了插入操作, 没有执行UPDATE,所以也没有为LAST_INSERT_ID传递值。所以之后执行SELECT LAST_INSERT_ID()返回的值不可确定, 要看当前连接在此之前执行过什么操作,如果没有执行过会影响LAST_INSERT_ID值的操作,那么返回值将是0, 不然就是该操作产生的值。所以,我们应该尽量避免使用这种方式。

UPDATE: 这个方法更容易解决单点问题,也不局限于两个服务器,只要对不同的服务器设置不同的初始值(但必须是连续的), 然后将增量变为服务器数就行了。

总结一下

我还是那句话,够用就好。当然,也不是说就不要去了解其它产品、方案了。又拍网也在使用一些新兴的产品, 比如Redis(在10年3月就开始在正式环境下使用了,算是比较早的使用者), 因为它的引入的确能够更好、更方便、更高效的解决我们的某些问题。 关键还是需要在使用前对其进行足够的了解。我会在后面的文章中介绍一下Redis的使用情况。

相关 [设计 主键 选择] 推荐:

分库设计中的主键选择

- Epile - zolazhou.com
在先前的文章《又拍网架构中的分库设计》中, 我有提到过MySQL分库设计中的主键选择问题. 在这篇文章里我想对这个问题进行展开讨论, 以此作为对上一篇文章的一个补充. 前面提到又拍网采用了全局唯一的字段作为主键. 比如拿照片表为例, 虽然不同用户的照片数据存放在不同的Shard(或者说MySQL节点/实例, 请参考《又拍网架构中的分库设计》)上, 但是每一张照片拥有整个站点唯一的ID作为标示.

网页设计中图片格式的选择技巧

- - 优设(UISDC)
网页设计中,切图输出是指设计师将手边绘制完成的原始图稿转换成网页用的图片格式、并交由下一位人员网页排版的重要步骤. 选择适合的图片格式不但可以将让设计得到合理的显示效果、甚至还可以有效的控制图档的档案大小,节省下载时间、有效的减少服务器的负担. 以设计师最常用的 Photoshop 以及 Illustrator 为例,都提供了「储存为网页用…」的功能.

游戏设计不宜作为新手入行职业选择

- - GamerBoom.com 游戏邦
作者:Jacob Stevens. 高中毕业时,我的数学老师送给我们几句话,“班上所有女生都想要成为海洋生物学家,所有男生都想要制作电子游戏. ”当然他这么说有些夸张,但他的观点无可非议:所有光鲜靓丽的事业都是靠来之不易的专业技术支撑的. 回到10年前,我自己被迫不得不给出相同的忠告. 作为家乡为数不多的开发者之一,我时常被问及要如何“进入游戏设计行业”.

选择合适创意的设计原则

- - 酷勤网-挖经验 [expanded by feedex.net]
 来源:uedwow.com用户体验规划师   2011-12-25. 经常有人问我怎样才能从头脑风暴中所获得的数以百计的创意中选出合适的一个. 除了靠直觉和经验外,还有一种方法可以帮我们来决定和界定设计原则. 在2007年以前,我们将这些原则称为“设计标准”,直到我看到了Rachel Hinman所编写的“大众计算工作室:移动用户体验设计原则”这篇文章.

合适的用户引导该如何选择与设计

- - 人人都是产品经理
在 设计产品的时候,往往都会遇到一个问题:产品内有一个地方需要提示引导用户去操作,又或者需要解释如何操作. 这个时候,如何 设计一个既能很好的提示用户又能保证良好用户体验的引导性提示就成了一个头疼的问题. 为什么说头疼呢?因为其实这些提示都是在原有产品的基础上附加的一个东西,而且往往不是用户有心理预期出现的东西.

网页设计师的必备选择20 +必需的Windows应用程序

- Pei - 博客园-首页原创精华区
今天给网页设计师推荐20几个windows下提高工作效率的应用程序,对于设计师来说是必不可少的,希望大家喜欢. 虽然我更喜欢使用Firebug时,我发现“开箱即用”的CSS编辑器要真正有用的. Skybound Stylizer已经出了一段时间,但最新版本,4.0,现在是出了很多令人难以置信的功能,以帮助您在开发过程中.

转载 选择

- bravusliu - caowumao的博客

CSS4 选择器

- iVane - 幸福收藏夹
CSS3 还没完全用上,CSS4 已经提上日程. 官方发布了 update to the working Selectors Level 4 spec,对选择器做了一些升级. 前端最大的优点就是技术更新快,可以经常学到新东西;最大的缺点也是技术更新快,要跟上潮流还真不是那么简单. 不过,这次更新有像“父选择器”这样让人兴奋的内容,让我们先睹为快,了解一下吧:.

JQuery 选择器

- - CSDN博客Web前端推荐文章
}

点击我

.    像上面这样把JavaSript代码和HTML代码混杂在一起的做法同样也非常不妥,因为它并没有将网页内容和行为分离,所以才有JQuery选择器的学习.

点击我

. //给class为demo的元素添加行为.

选择性闭嘴

- 蓓 - 土摩托日记
除了熟人之外,文青博客我追看的不多,总数不会超过10个,因为大多数这类博客的营养都欠奉. 一个是连岳,他的感情QA还是挺好看的,某些政论文字也还不错. 但这厮喜欢掺和科学的事儿,不止一次误导过读者. 就拿地震预报来说吧,他哪有资格评论. 看看这个报道,今天距离这则报道正好过去了两个月,可预报的地震仍然没有发生.