2014-09-05 79 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複製的豐富知識。

相關問題