日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用同一uuid作为两个字段的值_分库设计中的主键选择

發(fā)布時間:2024/4/17 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用同一uuid作为两个字段的值_分库设计中的主键选择 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在先前的文章《又拍網(wǎng)架構(gòu)中的分庫設(shè)計(jì)》中,我有提到過MySQL分庫設(shè)計(jì)中的主鍵選擇問題。在這篇文章里我想對這個問題進(jìn)行展開討論,以此作為對上一篇文章的一個補(bǔ)充。

前面提到又拍網(wǎng)采用了全局唯一的字段作為主鍵。比如拿照片表為例,雖然不同用戶的照片數(shù)據(jù)存放在不同的Shard(或者說MySQL節(jié)點(diǎn)/實(shí)例, 請參考《又拍網(wǎng)架構(gòu)中的分庫設(shè)計(jì)》)上,但是每一張照片擁有整個站點(diǎn)唯一的ID作為標(biāo)示。

為什么要全局唯一?

我們在對數(shù)據(jù)庫集群作擴(kuò)容時,為了保證負(fù)載的平衡,需要在不同的Shard之間進(jìn)行數(shù)據(jù)的移動,如果主鍵不唯一,我們就沒辦法這樣隨意的移動數(shù)據(jù)。起初,我們考慮采用組合主鍵來解決這個問題。一般會以user_id和一個自增的photo_id來作為主鍵,這的確能解決移動數(shù)據(jù)可能帶來的主鍵沖突問題,但是就像在“又拍網(wǎng)架構(gòu)中的分庫設(shè)計(jì)”中描述的那樣當(dāng)Shard之間的數(shù)據(jù)發(fā)生關(guān)系后,我們需要用更多的字段來組成主鍵以保證唯一性,因此主鍵的索引會變的很大,從而影響查詢性能,同時也會影響寫入性能。

其次,每個Shard由兩臺MySQL服務(wù)器組成,而這兩臺服務(wù)器采用master-master的復(fù)制方式,以保證每個Shard一直可寫。master-master復(fù)制方式必須保證在兩臺服務(wù)器上各自插入的數(shù)據(jù)有不同的主鍵,不然當(dāng)復(fù)制到另外一臺時就會出現(xiàn)主鍵重復(fù)錯誤。如果我們保證主鍵全局唯一,就自然的解決了這個問題。在沒有采用數(shù)據(jù)拆分的設(shè)計(jì)當(dāng)中,如果要用自增字段,可以參考這篇文章里的解決辦法。

可能的解決方案

UUID

或許可以采用UUID作為主鍵,但是UUID好長的一串,放在URL里好難看啊,有木有?當(dāng)然這個不是關(guān)鍵所在,更重要的原因還是性能。UUID的生成沒有順序性,所以在寫入時,需要隨機(jī)更改索引的不同位置,這就需要更多的IO操作,如果索引太大而不能存放在內(nèi)存中的話就更是如此。而UUID索引時,一個key需要32個字節(jié)(當(dāng)然如果采用二進(jìn)制形式存儲的話可以壓縮到16個字節(jié)),因此整個索引也會相對比較大。

MySQL自增字段

在單個MySQL數(shù)據(jù)庫的應(yīng)用中一般設(shè)置一個自增的字段就可以了,而在水平分庫的設(shè)計(jì)當(dāng)中,這種方法顯然不能保證全局唯一。那么我們可以單獨(dú)建立一個庫用來生成ID,在Shard中的每張表在這個ID庫中都有一個對應(yīng)的表,而這個對應(yīng)的表只有一個字段,這個字段是自增的。當(dāng)我們需要插入新的數(shù)據(jù),我們首先在ID庫中的相應(yīng)表中插入一條記錄,以此得到一個新的ID,然后將這個ID作為插入到Shard中的數(shù)據(jù)的主鍵。這個方法的缺點(diǎn)就是需要額外的插入操作,如果ID庫變的很大,性能也會隨之降低。所以一定要保證ID庫的數(shù)據(jù)集不要太大,一個辦法是定期清理前面的記錄。

引入其它工具

Redis、Memcached等都支持原子性的increment操作,而且因?yàn)樗鼈兊膬?yōu)秀性能可以減少寫入時的額外開銷,也許我們可以拿它們當(dāng)作序列生成器。Memcached的問題在于不持久性,所以我們不會考慮。而Redis也不是實(shí)時持久的,當(dāng)然也可以配置成實(shí)時的,但那樣怪怪的。當(dāng)然也有一些持久的工具,比如Kyoto Cabinet、Tokyo Cabinet、MongoDB等等,傳說中性能都不錯,但是引入其它工具會增加架構(gòu)的復(fù)雜程度,也會增加維護(hù)成本。我們的團(tuán)隊(duì)很小,精力有限,我們奉行夠用就好的原則,也就是沒有特別的原因,在可以接受的情況下,盡量用我們熟悉的工具解決問題。所以,我們還是來考慮一下怎么樣用MySQL來解決這個問題吧。

更好的方案

我們一開始就是采用了上面所描述的MySQL自增字段的方法,后來看到《Ticket Servers: Distributed Unique Primary Keys on the Cheap》這篇文章里所描述的方法,豁然開朗。我經(jīng)常這樣想:如果沒有那些開源產(chǎn)品、沒有那些無私分享經(jīng)驗(yàn)的人,光憑我們自己的能力能做到什么程度。很感謝那些人,所以我也盡量多的分享一些自己的經(jīng)驗(yàn)。

我先描述一下Flickr那篇文章里所描述的方法,他們使用了REPLACE INTO這個MySQL的擴(kuò)展功能。REPLACE INTO和INSERT的功能一樣,但是當(dāng)使用REPLACE INTO插入新數(shù)據(jù)行時,如果新插入的行的主鍵或唯一鍵(UNIQUE Key)已有的行重復(fù)時,已有的行會先被刪除,然后再將新數(shù)據(jù)行插入。你可以放心,這是原子操作。

建立類似下面的表:

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;

當(dāng)需要獲得全局唯一ID時,執(zhí)行下面的SQL語句:

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

第一次執(zhí)行這個語句后,ticket64表將包含以下數(shù)據(jù):

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

以后再次執(zhí)行前面的語句,stub字段值為’a’的行已經(jīng)存在,所以MySQL會先刪除這一行,再插入。因此,第二次執(zhí)行后,ticket64表還是只有一行數(shù)據(jù),只是id字段的值為2。這個表將一直只有一行數(shù)據(jù)。

Flickr為Photo, Group, Account, Task各自建立了一張ticket表以保持各自的ID的連續(xù)性。其它業(yè)務(wù)表的ID都使用同一個ticket表產(chǎn)生。

不錯吧,其實(shí)還可以更棒。比如,只需要一張ticket表就可以為所有的業(yè)務(wù)表提供各自連續(xù)的ID。下面,來看一下我們的方法。首先來看一下表結(jié)構(gòu):

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

注意區(qū)別,id字段不是自增的,也不是主鍵。在使用前,我們需要先插入一些初始化數(shù)據(jù):

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

接下來,我們可以通過執(zhí)行下面的SQL語句來獲得新的照片ID:

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

我們執(zhí)行了一個更新操作,將id字段增加1,并將增加后的值傳遞到LAST_INSERT_ID函數(shù),從而指定了LAST_INSERT_ID的返回值。

實(shí)際上,我們不一定需要預(yù)先指定序列的名字。如果我們現(xiàn)在需要一種新的序列,我們可以直接執(zhí)行下面的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擴(kuò)展,這個擴(kuò)展的功能也和INSERT一樣插入一行新的記錄,但是當(dāng)新插入的行的主鍵或唯一鍵(UNIQUE Key)和已有的行重復(fù)時,會對已有行進(jìn)行UPDATE操作。

需要注意的是,當(dāng)我們第一次執(zhí)行上面的語句時,因?yàn)檫€沒有name為’new_business’的字段,所以正常的執(zhí)行了插入操作,沒有執(zhí)行UPDATE,所以也沒有為LAST_INSERT_ID傳遞值。所以之后執(zhí)行SELECT LAST_INSERT_ID()返回的值不可確定,要看當(dāng)前連接在此之前執(zhí)行過什么操作,如果沒有執(zhí)行過會影響LAST_INSERT_ID值的操作,那么返回值將是0,不然就是該操作產(chǎn)生的值。所以,我們應(yīng)該盡量避免使用這種方式。

UPDATE: 這個方法更容易解決單點(diǎn)問題,也不局限于兩個服務(wù)器,只要對不同的服務(wù)器設(shè)置不同的初始值(但必須是連續(xù)的),然后將增量變?yōu)榉?wù)器數(shù)就行了。

總結(jié)一下

我還是那句話,夠用就好。當(dāng)然,也不是說就不要去了解其它產(chǎn)品、方案了。又拍網(wǎng)也在使用一些新興的產(chǎn)品,比如Redis(在10年3月就開始在正式環(huán)境下使用了,算是比較早的使用者),因?yàn)樗囊氲拇_能夠更好、更方便、更高效的解決我們的某些問題。關(guān)鍵還是需要在使用前對其進(jìn)行足夠的了解。我會在后面的文章中介紹一下Redis的使用情況。

總結(jié)

以上是生活随笔為你收集整理的用同一uuid作为两个字段的值_分库设计中的主键选择的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。