2014-09-05 20 views
9

Metoda PostgreS Instagrama implementująca niestandardowe identyfikatory do Shardingu jest świetna, ale potrzebuję implementacji w MySQL.Czy AUTO_INCREMENT może być bezpiecznie użyty przed PRZERWANIEM w MySQL

Więc przekształcone metodę znaleziony na dnie tym blogu, tutaj: http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram

MySQL Wersja:

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 

tabela wygląda mniej więcej tak:

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

Pytanie :

  1. Występuje tu problem współbieżności. Przy spawaniu 100 wątków i uruchomionych wstawek otrzymuję zduplikowane wartości sekwencji, co oznacza, że ​​dwa wyzwalacze otrzymują tę samą wartość auto_increment. Jak mogę to naprawić?

Próbowałem utworzyć nową tabelę, np. "tablename_seq", z jednym wierszem, licznikiem do przechowywania moich własnych wartości auto_increment, a następnie aktualizowaniem tej tabeli wewnątrz TRIGGER, ale problemem jest to, że nie mogę ZABLOKOWAĆ tabeli w procedurze zapisanej (trigger), więc mam dokładna sam problem, nie mogę zagwarantować, licznik będzie wyjątkowy między wyzwalaczy :(

jestem zakłopotany i naprawdę doceni wszelkie wskazówki

Możliwe rozwiązanie:.!

  1. MySQL 5.6 ma UUID_SHORT(), który generuje unikalne inkrementujące wartości, które są gwarantowane, że są unikatowe. W praktyce nazywa się to tym, że każde połączenie zwiększa wartość +1. T seq_id = (SELECT UUID_SHORT()); wydaje się, że usuwa problem współbieżności. Efektem ubocznym tego jest to, że obecnie (w przybliżeniu) nie może wystąpić więcej niż 1024 wstawek na milisekundę w całym systemie. Jeśli więcej, to możliwe, że wystąpił błąd DUPLICATE PRIMARY KEY. Dobrą wiadomością jest to, że w benchmarkach na moim komputerze uzyskuję ~ 3000 insertów/s z lub bez wyzwalacza conting UUID_SHORT(), więc wydaje się, że wcale nie spowalnia to.
+0

Czy prosty unikalny indeks w kolumnie tabeli AUTO_INCREMENT wykona swoją pracę? w ten sposób generujesz alert, jeśli drugi wygenerowany identyfikator jest taki, który już istnieje w tabeli. i prawdopodobnie możesz złapać to ostrzeżenie i pozwól, aby procedura ponownie się zaczęła. nie jest to najbardziej skuteczny sposób, ale najprawdopodobniej zadziała. – dom

+0

Może coś takiego [SQL Fiddle] (http://sqlfiddle.com/#!2/a6fd4/1) może dostarczyć przynajmniej pomysłów na wdrożenie tego, czego potrzebujesz. – wchiquito

+0

@wchiquito Bardzo dziękuję za poświęcenie czasu na utworzenie linku [SQL Fiddle] (http://sqlfiddle.com/#!2/a6fd4/1). Jednak po uruchomieniu wydaje się mieć ten sam problem współbieżności. Na przykład. 11828889504449540 został powtórzony 3 razy jako ID 3-szty w teście. Czy to działa dla ciebie? To sprytny pomysł na użycie insert/last_insert_id na innej tabeli w przechowywanym proc! Jednak wydaje się, że nie działa. – jsidlosky

Odpowiedz

2

Poniższy SQL Fiddle generuje wyjście, jak pokazano poniżej:

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) 

Zebrane odpowiedź, czy to naprawdę rozwiązuje swoje potrzeby.

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) 

Zobacz db-fiddle.

+0

Nie mogę otworzyć linku fiddle sql. – Sisyphus

+0

Skrzypce SQL są nieosiągalne, dlatego odpowiedź staje się bez znaczenia i powinna zostać usunięta. – RandomSeed

+0

@ Syzyfa: Zaktualizowana odpowiedź. Dziękuję Ci. – wchiquito

2

Alternatywą jest pobieranie bloków automatycznych numerów inkrementacji. Jeśli ustawisz MySQLs auto increment increment na wartość zbliżoną do 1000, proces może wykonać wstawienie w tabeli "sekwencji" i uzyskać wartość automatycznego przyrostu. Proces ten wie, że ma 1000 kolejnych numerów, z których może korzystać, zaczynając od tej liczby, bez konfliktów. Nie ma potrzeby rejestrowania każdej inkrementacji w centralnym stole, jeśli wszystko, co nagrywasz, jest liczbą.

Jest to najczęściej używane w wielu konfiguracjach głównych oprócz automatycznego przesunięcia inkrementalnego. Możesz także wybrać trasę z wieloma wzorcami i wstawić ją na różnych wzorcach.Automatyczny przyrost i kompensacja nie zapewni żadnych konfliktów. Wymagałoby to solidnej znajomości replikacji MySQL.

Powiązane problemy