2014-09-05 10 views
9

Il metodo Postgres di Instagram per implementare ID personalizzati per Sharding è ottimo, ma ho bisogno dell'implementazione in MySQL.AUTO_INCREMENT può essere utilizzato in modo sicuro in PRINCIPALE TRIGGER in MySQL

Così, ho trasformato il metodo trovato in fondo a questo blog, qui: http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram

MySQL Version:

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 

La tabella si presenta più o meno così:

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

domanda :

  1. C'è un problema di concorrenza qui. Quando si generano 100 thread e si eseguono inserimenti, ottengo valori di sequenza duplicati, ovvero due trigger ricevono lo stesso valore auto_increment. Come posso risolvere questo?

Ho provato a creare un nuovo tavolo, ad es. "tablename_seq", con una riga, un contatore per memorizzare i miei valori auto_increment, quindi facendo gli aggiornamenti a quella tabella all'interno del TRIGGER, ma il problema è che non riesco a BLOCCARE la tabella in una stored procedure (trigger), quindi ho il esattamente lo stesso problema, non posso garantire un contatore per essere unico fra gli inneschi :(

sono perplesso e realmente apprezzerebbe eventuali suggerimenti

Soluzione possibile.!

  1. MySQL 5.6 ha UUID_SHORT() che genera valori incrementali univoci che sono garantiti come univoci.Presenta in pratica quando si chiama questo, ogni chiamata incrementa il valore +1. T seq_id = (SELEZIONA UUID_SHORT()); sembra rimuovere il problema di concorrenza. L'effetto collaterale di questo è che ora (all'incirca) non più di 1024 inserimenti possono accadere per millisecondo nell'intero sistema. Se altri lo fanno, allora è possibile che si verifichi un errore DUPLICATE PRIMARY KEY. La buona notizia è che nei benchmark sulla mia macchina ottengo ~ 3000 inserti/i con o senza il trigger che contamina UUID_SHORT(), quindi non sembra rallentarlo affatto.
+0

sarebbe un semplice indice univoco sulla colonna della tabella AUTO_INCREMENT fare il tuo lavoro? in questo modo si genera un avviso se un secondo ID generato è già esistente nella tabella. e probabilmente puoi prendere questo avviso e lasciare che la procedura si ripresenti. non è il modo più efficiente ma molto probabilmente funzionerebbe. – dom

+0

Forse qualcosa come questo [SQL Fiddle] (http://sqlfiddle.com/#!2/a6fd4/1) può darti, almeno, idee su come implementare ciò di cui hai bisogno. – wchiquito

+0

@wchiquito Grazie mille per aver dedicato del tempo a creare il collegamento [SQL Fiddle] (http://sqlfiddle.com/#!2/a6fd4/1). Tuttavia, dopo averlo eseguito, sembra che abbia lo stesso problema di concorrenza. Per esempio. 11828889504449540 è stato ripetuto 3 volte come ID 3-sesto nel test. Ha funzionato per te? È un'idea geniale usare un insert/last_insert_id su un'altra tabella in un proc memorizzato! Tuttavia, non sembra funzionare. – jsidlosky

risposta

2

Il seguente SQL Fiddle genera un output come illustrato di seguito:

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) 

Accettare la risposta se si risolve veramente il vostro bisogno.

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) 

Vedi db-fiddle.

+0

Non riesco ad aprire il collegamento sql fiddle. – Sisyphus

+0

SQL Fiddle è irraggiungibile, pertanto la risposta diventa priva di significato e deve essere eliminata. – RandomSeed

+0

@Sisyphus: risposta aggiornata. Grazie. – wchiquito

2

Un'alternativa è per afferrare blocchi di numeri di incremento automatico. Se si imposta MySQL auto increment increment su qualcosa come 1000, un processo può fare un inserimento nella tabella "sequenza" e ottenere il valore di incremento automatico. Il processo quindi sa che ha 1000 numeri sequenziali che può usare, a partire da quel numero, che sarà privo di conflitti. Non è necessario registrare ogni incremento in un tavolo centrale se tutto ciò che si sta registrando è un numero.

Questo è più comunemente utilizzato in più configurazioni master oltre all'offset di incremento automatico. Puoi anche seguire la rotta master multipla e inserirla nei vari master.L'incremento e l'offset dell'aumento automatico non garantiscono conflitti. Ciò richiederebbe una solida conoscenza della replica di MySQL.

Problemi correlati