2014-09-05 76 views
9

Instagram的Postgres实现用于Sharding的定制Ids的方法非常棒,但我需要在MySQL中实现。AUTO_INCREMENT是否可以安全地用于MySQL中的BEFORE TRIGGER中

所以,我转换这个博客的底部,在这里找到了方法:http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram

的MySQL版本:

CREATE TRIGGER shard_insert BEFORE INSERT ON tablename 
FOR EACH ROW BEGIN 

DECLARE seq_id BIGINT; 
DECLARE now_millis BIGINT; 
DECLARE our_epoch BIGINT DEFAULT 1314220021721; 
DECLARE shard_id INT DEFAULT 1; 

SET now_millis = (SELECT UNIX_TIMESTAMP(NOW(3)) * 1000); 
SET seq_id = (SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = "dbname" AND TABLE_NAME = "tablename"); 
SET NEW.id = (SELECT ((now_millis - our_epoch) << 23) | (shard_id << 10) | (SELECT MOD(seq_id, 1024))); 
END 

表看起来大致是这样的:

CREATE TABLE tablename (
    id BIGINT AUTO_INCREMENT, 
    ... 
) 

问题:

  1. 这里有一个并发问题。当产生100个线程并运行插入时,我得到重复的序列值,这意味着两个触发器获得相同的auto_increment值。我怎样才能解决这个问题?

我试着创建一个新表,例如“tablename_seq”,有一行,一个计数器来存储我自己的auto_increment值,然后在TRIGGER内部对该表进行更新,但问题是我不能在一个存储过程(触发器)中锁定表,所以我拥有完全相同的问题,我不能保证一个计数器设置为触发之间:(独特

我难倒真的希望任何提示

可能的解决方案:!

  1. 的MySQL 5.6拥有UUID_SHORT()生成唯一的递增值,这些值保证是唯一的。实际上,在调用这个值时,每次调用都会增加+1的值。通过使用:SE T seq_id =(SELECT UUID_SHORT());它似乎消除了并发问题。这样做的副作用是现在(大致)在整个系统中每毫秒不超过1024个插入。如果还有更多,则可能会出现DUPLICATE PRIMARY KEY错误。好消息是,在我的机器上的基准测试中,我得到了〜3,000个插入/秒,但没有连续触发UUID_SHORT(),所以它看起来并没有减慢它的速度。
+0

会在表列AUTO_INCREMENT上做一个简单的唯一索引来完成你的工作吗?这样,如果第二个生成的ID是表中已经存在的ID,就会生成警报。并且您可能会捕获此警报,让程序重新运行。不是最有效的方法,但最有可能的工作。 – dom 2014-09-07 06:56:37

+0

也许像这样[SQL小提琴](http://sqlfiddle.com/#!2/a6fd4/1)可以给你,至少,如何实现你所需要的想法。 – wchiquito 2014-09-07 11:10:34

+0

@wchiquito非常感谢您花时间创建[SQL Fiddle](http://sqlfiddle.com/#!2/a6fd4/1)链接。但是,在运行它时,它似乎具有相同的并发问题。例如。 11828889504449540被重复3次作为测试中的第3至第6个ID。它对你有用吗?在存储过程的另一个表上使用insert/last_insert_id是一个聪明的想法!但是,它似乎没有工作。 – jsidlosky 2014-09-07 18:49:59

回答

2

以下SQL Fiddle产生输出,如下图所示:

Welcome to the MySQL monitor. Commands end with ; or \g. 
Your MySQL connection id is 45 
Server version: 5.5.35-1 

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 

Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners. 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 

mysql> use test; 
Reading table information for completion of table and column names 
You can turn off this feature to get a quicker startup with -A 

Database changed 
mysql> select `id` from `tablename`; 
+-------------------+ 
| id    | 
+-------------------+ 
| 11829806563853313 | 
| 11829806563853314 | 
| 11829806563853315 | 
| 11829806563853316 | 
| 11829806563853317 | 
| 11829806563853318 | 
| 11829806563853319 | 
| 11829806563853320 | 
| 11829806563853321 | 
| 11829806563853322 | 
| 11829806563853323 | 
| 11829806563853324 | 
| 11829806563853325 | 
| 11829806563853326 | 
| 11829806563853327 | 
| 11829806563853328 | 
| 11829806563853329 | 
| 11829806563853330 | 
| 11829806563853331 | 
| 11829806563853332 | 
| 11829806563853333 | 
| 11829806563853334 | 
| 11829806563853335 | 
| 11829806563853336 | 
| 11829806563853337 | 
| 11829806563853338 | 
| 11829806563853339 | 
| 11829806563853340 | 
| 11829806563853341 | 
| 11829806563853342 | 
| 11829806563853343 | 
| 11829806563853344 | 
| 11829806563853345 | 
| 11829806563853346 | 
| 11829806563853347 | 
| 11829806563853348 | 
| 11829806563853349 | 
| 11829806563853350 | 
| 11829806563853351 | 
| 11829806563853352 | 
+-------------------+ 
40 rows in set (0.01 sec) 

接受的答案,如果它真的解决您的需要。

UPDATE

Welcome to the MySQL monitor. Commands end with ; or \g. 
Your MySQL connection id is 46 
Server version: 5.5.35-1 

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. 

Oracle is a registered trademark of Oracle Corporation and/or its 
affiliates. Other names may be trademarks of their respective 
owners. 

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 

mysql> use test; 
Reading table information for completion of table and column names 
You can turn off this feature to get a quicker startup with -A 

Database changed 
mysql> DELIMITER // 

mysql> DROP FUNCTION IF EXISTS `nextval`// 
Query OK, 0 rows affected, 1 warning (0.00 sec) 

mysql> DROP TRIGGER IF EXISTS `shard_insert`// 
Query OK, 0 rows affected (0.00 sec) 

mysql> DROP TABLE IF EXISTS `tablename_seq`, `tablename`; 
Query OK, 0 rows affected (0.00 sec) 

mysql> CREATE TABLE `tablename_seq` (
    -> `seq` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY 
    ->)// 
Query OK, 0 rows affected (0.00 sec) 

mysql> CREATE TABLE `tablename` (
    -> `id` BIGINT UNSIGNED PRIMARY KEY 
    ->)// 
Query OK, 0 rows affected (0.00 sec) 

mysql> CREATE FUNCTION `nextval`() 
    -> RETURNS BIGINT UNSIGNED 
    -> DETERMINISTIC 
    -> BEGIN 
    -> DECLARE `_last_insert_id` BIGINT UNSIGNED; 
    -> INSERT INTO `tablename_seq` VALUES (NULL); 
    -> SET `_last_insert_id` := LAST_INSERT_ID(); 
    -> DELETE FROM `tablename_seq` 
    -> WHERE `seq` = `_last_insert_id`; 
    -> RETURN `_last_insert_id`; 
    -> END// 
Query OK, 0 rows affected (0.00 sec) 

mysql> CREATE TRIGGER `shard_insert` BEFORE INSERT ON `tablename` 
    -> FOR EACH ROW 
    -> BEGIN 
    -> DECLARE `seq_id`, `now_millis` BIGINT UNSIGNED; 
    -> DECLARE `our_epoch` BIGINT UNSIGNED DEFAULT 1314220021721; 
    -> DECLARE `shard_id` INT UNSIGNED DEFAULT 1; 
    -> SET `now_millis` := `our_epoch` + UNIX_TIMESTAMP(); 
    -> SET `seq_id` := `nextval`(); 
    -> SET NEW.`id` := (SELECT (`now_millis` - `our_epoch`) << 23 | 
    ->       `shard_id` << 10 | 
    ->       MOD(`seq_id`, 1024) 
    ->     ); 
    -> END// 
Query OK, 0 rows affected (0.00 sec) 

mysql> INSERT INTO `tablename` 
    -> VALUES 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0), 
    -> (0), (0), (0), (0), (0)// 
Query OK, 40 rows affected (0.00 sec) 
Records: 40 Duplicates: 0 Warnings: 0 

mysql> DELIMITER ; 

mysql> SELECT `id` FROM `tablename`; 
+-------------------+ 
| id    | 
+-------------------+ 
| 12581084357198849 | 
| 12581084357198850 | 
| 12581084357198851 | 
| 12581084357198852 | 
| 12581084357198853 | 
| 12581084357198854 | 
| 12581084357198855 | 
| 12581084357198856 | 
| 12581084357198857 | 
| 12581084357198858 | 
| 12581084357198859 | 
| 12581084357198860 | 
| 12581084357198861 | 
| 12581084357198862 | 
| 12581084357198863 | 
| 12581084357198864 | 
| 12581084357198865 | 
| 12581084357198866 | 
| 12581084357198867 | 
| 12581084357198868 | 
| 12581084357198869 | 
| 12581084357198870 | 
| 12581084357198871 | 
| 12581084357198872 | 
| 12581084357198873 | 
| 12581084357198874 | 
| 12581084357198875 | 
| 12581084357198876 | 
| 12581084357198877 | 
| 12581084357198878 | 
| 12581084357198879 | 
| 12581084357198880 | 
| 12581084357198881 | 
| 12581084357198882 | 
| 12581084357198883 | 
| 12581084357198884 | 
| 12581084357198885 | 
| 12581084357198886 | 
| 12581084357198887 | 
| 12581084357198888 | 
+-------------------+ 
40 rows in set (0.00 sec) 

db-fiddle

+0

我无法打开sql小提琴链接。 – Sisyphus 2016-09-06 17:46:01

+0

SQL小提琴是无法访问的,因此答案变得毫无意义,应该删除。 – RandomSeed 2017-05-05 07:55:13

+0

@Sisyphus:更新了答案。谢谢。 – wchiquito 2017-07-11 14:31:11

2

另一种方法是抓取自动增量数字块。如果您将MySQL auto increment increment设置为1000,则进程可以在“序列”表中插入并获取自动递增值。然后该过程知道它有1000个可以使用的序列号,从该号码开始,将没有冲突。如果您正在记录的是一个数字,则无需在中央表格中记录每个增量。

除了自动增量偏移之外,这是最常用于多个主设置的。你也可以去多个主路线,并插入不同的主人。自动增量增量和偏移量将确保没有冲突。这需要了解MySQL复制的丰富知识。

相关问题