2009-04-30 6 views
10

Nous utilisons beaucoup MySQL avec le moteur de stockage InnoDB et les transactions, et nous avons rencontré un problème: nous avons besoin d'une manière sympa d'émuler les SEQUENCES d'Oracle dans MySQL. Les exigences sont les suivantes: - soutien concurrency - la sécurité des transactions - performance maximum (sens réduisant au minimum les verrous et les blocages)Emulation d'une SEQUENCE transactionnelle dans MySQL

Nous ne se soucient pas si certaines valeurs ne seront pas utilisées, à savoir les lacunes dans l'ordre sont ok. Il existe un moyen facile d'archiver cela en créant une table InnoDB séparée avec un compteur, mais cela signifie qu'il participera à la transaction et introduira des verrous et des attentes. Je pense à essayer une table MyISAM avec des verrous manuels, d'autres idées ou bonnes pratiques?

Répondre

14

Si l'auto-incrément est pas assez bon pour vos besoins, vous pouvez créer un mécanisme de séquence atomique avec n séquences nommées comme ceci:

Créer une table pour stocker vos séquences:

CREATE TABLE sequence (
    seq_name varchar(20) unique not null, 
    seq_current unsigned int not null 
); 

en supposant que vous avez une ligne pour 'foo' Dans la table, vous pouvez obtenir atomiquement l'ID de séquence suivant comme ceci:

UPDATE sequence SET seq_current = (@next := seq_current + 1) WHERE seq_name = 'foo'; 
SELECT @next; 

Aucun verrou requis. Les deux instructions doivent être exécutées dans la même session, de sorte que la variable locale @next soit réellement définie lors de la sélection.

+0

Merci - c'est plus ou moins ce que nous sommes venus après tout - procédure stockée qui fait presque exactement cela. –

+1

Si cet enregistrement n'existe pas, utilisez cette requête. 'INSERT INTO séquence (seq_name, seq_current) VALEURS ('foo', (@next: = 1)) SUR LA CLÉ DUPLICATE MISE À JOUR seq_current = (@next: = seq_current + 1);' alors appeler 'SELECT @next;' – iwat

+0

vous pouvez combiner le commentaire de Iwat avec la réponse de qu1j0t3: 'INSERT INTO sequences2 (seq_name, seq_current) VALUES ('foo', LAST_INSERT_ID (1)) Duplicate KEY UPDATE seq_current = LAST_INSERT_ID (seq_current + 1);' puis appelez 'SELECT LAST_INSERT_ID();', ou si vous utilisez un pilote d'application, utilisez l'appel intégré auto-increment (par exemple 'Statement # getGeneratedKeys' dans jdbc) – Alden

0

La colonne Identité MySQL de la table ne gère-t-elle pas cela?

CREATE TABLE nom_table ( id INTEGER AUTO_INCREMENT PRIMARY KEY )

Vous cherchez à utiliser pour autre chose que simplement insérer dans une autre table? Si vous écrivez en utilisant un langage procédural (au lieu de SQL seulement), alors l'autre option serait de créer une table contenant une seule valeur entière (ou entier long) et une procédure stockée qui l'a verrouillée, sélectionnée à partir de là, l'a incrémenté et déverrouillé avant de retourner la valeur.

(Remarque - toujours incrémenter avant de retourner la valeur - il maximiser la chance de ne pas obtenir des doublons s'il y a des erreurs -. Ou envelopper le tout dans une transaction)

Vous appelleriez alors ceci indépendamment de votre insertion/mise à jour principale (afin de ne pas être coincé dans les transactions créées automatiquement par le mécanisme d'appel), puis passez-le comme paramètre à l'endroit où vous voulez l'utiliser. Parce qu'il est indépendant du reste des choses que vous faites, il devrait être rapide et éviter les problèmes de verrouillage. Même si vous avez vu une erreur causée par le verrouillage (improbable, sauf si vous surchargez la base de données), vous pouvez simplement l'appeler une deuxième/troisième fois.

+0

Merci, votre deuxième approche est probablement ce que j'avais à l'esprit plus ou moins , et les détails aident beaucoup. Des commentaires sur quel moteur de stockage est le mieux pour une telle table? –

+0

Je ne connais pas très bien MySQL, donc je ne peux pas vraiment vous le conseiller (je l'ai fait sur SQL Server). Ce que je dirais, c'est que je ne pense pas que ce soit si complexe que la meilleure approche serait potentiellement d'utiliser ce que vous utilisez pour le reste de vos affaires et d'économiser de la complexité. –

9

La bonne façon de le faire est donnée dans le MySQL manual:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1); 
SELECT LAST_INSERT_ID(); 
+1

Je ne vois pas la logique ici. 'LAST_INSERT_ID' n'a de sens que pour les champs auto-incrémentés. Mais si c'est déjà auto-incrémenté, pourquoi le besoin de l'incrémenter manuellement? – devios1

+0

L'instruction de mise à jour n'aurait aucun effet sur LAST_INSERT_ID si elle n'était pas définie manuellement. Cela peut être utilisé comme une astuce pour extraire des données d'une instruction de mise à jour sans utiliser une variable définie par l'utilisateur distincte. –

+0

Cette approche est correcte et le commentaire de @devios est trompeur. 'LAST_INSERT_ID (expr)' n'a rien à voir avec l'auto-incrémentation. – baf

4

Nous sommes une société de jeux de transaction élevée et avons besoin de ce genre de solutions pour nos besoins. L'une des caractéristiques des séquences Oracle était également la valeur d'incrément qui pouvait également être définie.

La solution utilise DUPLICATE KEY.

CREATE TABLE sequences (
    id BIGINT DEFAULT 1, 
    name CHAR(20), 
    increment TINYINT, 
    UNIQUE KEY(name) 
); 

Pour obtenir l'index suivant:

Résumé ce qui suit à une procédure stockée ou une fonction sp_seq_next_val(VARCHAR):

INSERT INTO sequences (name) VALUES ("user_id") ON DUPLICATE KEY UPDATE id = id + increment;<br/> 
SELECT id FROM sequences WHERE name = "user_id"; 
+0

Hm intéressant, merci pour le pointeur. Est-ce que ON DUPLICATE KEY est toujours atomique avec INSERT dans MySQL? –

+0

@Michael: Oui, ON DUPLICATE KEY est atomique. – kopos

Questions connexes