2009-04-30 22 views
10

Estamos usando MySQL con el motor de almacenamiento InnoDB y transacciones, y nos encontramos con un problema: necesitamos una buena manera de emular las SECUENCIAS de Oracle en MySQL. Los requisitos son: - el soporte de concurrencia - seguridad de las transacciones - rendimiento máximo (que significa bloqueos y estancamientos minimizando)Emulando una SECUENCIA de transacción segura en MySQL

No importa si no se utilizarán algunos de los valores, es decir, huecos en la secuencia están bien. Hay una manera fácil de archivarlo creando una tabla InnoDB separada con un contador, sin embargo, esto significa que tomará parte en la transacción e introducirá bloqueos y esperas. Estoy pensando en probar una tabla MyISAM con bloqueos manuales, otras ideas o mejores prácticas.

Respuesta

14

Si incremento automático no es lo suficientemente bueno para sus necesidades, puede crear un mecanismo secuencia atómica con n secuencias nombradas como esto:

Crear una tabla para almacenar sus secuencias:

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

Asumiendo que tiene una fila de 'foo' En la tabla se puede obtener el siguiente atómicamente secuencia de Identificación así:

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

No hay cerraduras requeridos. Ambas sentencias deben ejecutarse en la misma sesión, de modo que la variable local @next se defina realmente cuando se realice la selección.

+0

Gracias - eso es más o menos lo que se nos ocurrió después de todo - procedimiento almacenado que hace casi exactamente eso. –

+1

En caso de que ese registro no exista, utilice esta consulta. 'INSERT INTO sequence (seq_name, seq_current) VALUES ('foo', (@next: = 1)) ON DUPLICATE KEY ACTUALIZACIÓN seq_current = (@next: = seq_current + 1);' then call 'SELECT @next;' – iwat

+0

es posible que desee combinar el comentario de iwat con la respuesta de qu1j0t3: 'INSERT INTO sequences2 (seq_name, seq_current) VALUES ('foo', LAST_INSERT_ID (1)) ON DUPLICATE KEY UPDATE seq_current = LAST_INSERT_ID (seq_current + 1);' luego, llame a 'SELECT LAST_INSERT_ID();', o si está utilizando un controlador de aplicación, use la función de autoincremento de la llamada (ej. 'Statement # getGeneratedKeys' en jdbc) – Alden

0

¿No manejará esto la columna Identidad de MySQL en la tabla?

CREATE TABLE nombre_tabla ( ID INTEGER AUTO_INCREMENT PRIMARY KEY )

O usted está buscando para usarlo para algo más que simplemente insertando en otra tabla?

Si también está escribiendo utilizando un lenguaje de procedimientos (en lugar de solo SQL), la otra opción sería crear una tabla que contenga un valor entero único (o entero largo) y un procedimiento almacenado que lo bloqueó, seleccionado de él, lo incrementó y lo desbloqueó antes de devolver el valor.

(Nota - Siempre incrementar antes de devolver el valor - es maximizar la posibilidad de no obtener duplicados si hay errores -. O envolver todo el asunto en una transacción)

A continuación, llamar a esto independientemente de su inserción/actualización principal (para que no quede atrapado en ninguna transacción creada automáticamente por el mecanismo de llamada) y luego pasarlo como un parámetro a donde quiera usarlo.

Porque es independiente del resto de las cosas que está haciendo, debe ser rápido y evitar problemas de bloqueo. Incluso si ve un error causado por el bloqueo (poco probable a menos que esté sobrecargando la base de datos), puede llamarlo por segunda o tercera vez.

+0

Gracias, el segundo enfoque es probablemente lo que tenía en mente más o menos y los detalles ayudan mucho. ¿Algún comentario sobre qué motor de almacenamiento es mejor para una mesa como esta? –

+0

Realmente no conozco bien a MySQL, así que no puedo aconsejar sobre eso (lo he hecho en SQL Server). Lo que yo diría es que no creo que sea tan complejo, así que el mejor enfoque sería utilizar lo que sea que esté usando para el resto de sus cosas y ahorrar complejidad añadida. –

9

La forma correcta de hacer esto se da en el MySQL manual:

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

No veo la lógica aquí. 'LAST_INSERT_ID' solo es significativo para los campos de incremento automático. Pero si ya se está autoincrementando, ¿por qué la necesidad de incrementarlo manualmente? – devios1

+0

La declaración de actualización no tendría ningún efecto en LAST_INSERT_ID si no se configurara manualmente. Esto se puede utilizar como un truco para obtener datos de una declaración de actualización sin utilizar una variable separada definida por el usuario. –

+0

Este enfoque es correcto y el comentario de @devios es engañoso. 'LAST_INSERT_ID (expr)' no tiene nada que ver con el incremento automático. – baf

4

Somos una gran compañía de juegos de transacciones y necesitamos este tipo de soluciones para nuestras necesidades. Una de las características de las secuencias de Oracle también era el valor de incremento que también se podía establecer.

La solución usa DUPLICATE KEY.

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

Para obtener el índice siguiente:

Resumen de la siguiente con un procedimiento almacenado o una función 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 interesante, gracias por el puntero. ¿Está ON DUPLICATE KEY siempre atómico con INSERT en MySQL? –

+0

@Michael: Sí, ON DUPLICATE KEY es atómico. – kopos

Cuestiones relacionadas