sql >> Base de Datos >  >> RDS >> Mysql

Campo de filas secuenciales contiguas de MySQL incluso al eliminar e insertar

Sé que hay mucho aquí. Traté de documentarlo bastante bien dentro del código y aquí y allá. Utiliza procedimientos almacenados. Naturalmente, puede extraer el código y no usar ese método. Utiliza una tabla principal que alberga los siguientes incrementadores disponibles. Utiliza seguro INNODB Bloqueos de intención para la concurrencia. Tiene una tabla de reutilización y procesos almacenados para respaldarlo.

De ninguna manera usa la tabla myTable . Se muestra allí para su propia imaginación en función de los comentarios bajo su pregunta. El resumen de eso es que sabe que tendrá espacios en DELETE . Quieres una manera ordenada de reutilizar esos espacios, esos números de secuencia. Entonces, cuando DELETE una fila, use los procesos almacenados en consecuencia para agregar ese número. Naturalmente, hay un proceso almacenado para obtener el siguiente número de secuencia para su reutilización y otras cosas.

Para fines de prueba, su sectionType ='dispositivos'

¡Y lo mejor de todo es que está probado!

Esquema:

create table myTable
(   -- your main table, the one you cherish
    `id` int auto_increment primary key, -- ignore this
    `seqNum` int not null, -- FOCUS ON THIS
    `others` varchar(100) not null
) ENGINE=InnoDB;

create table reuseMe
(   -- table for sequence numbers to reuse
    `seqNum` int not null primary key, -- FOCUS ON THIS
    `reused` int not null -- 0 upon entry, 1 when used up (reused)
    -- the primary key enforces uniqueness
) ENGINE=InnoDB;;

CREATE TABLE `sequences` (
    -- table of sequence numbers system-wide
    -- this is the table that allocates the incrementors to you
    `id` int NOT NULL AUTO_INCREMENT,
    `sectionType` varchar(200) NOT NULL,
    `nextSequence` int NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `sectionType` (`sectionType`)
) ENGINE=InnoDB;
INSERT sequences(sectionType,nextSequence) values ('devices',1); -- this is the focus
INSERT sequences(sectionType,nextSequence) values ('plutoSerialNum',1); -- not this
INSERT sequences(sectionType,nextSequence) values ('nextOtherThing',1); -- not this
-- the other ones are conceptuals for multi-use of a sequence table

Proceso almacenado:uspGetNextSequence

DROP PROCEDURE IF EXISTS uspGetNextSequence;
DELIMITER $$
CREATE PROCEDURE uspGetNextSequence(p_sectionType varchar(200))
BEGIN
    -- a stored proc to manage next sequence numbers handed to you.
    -- driven by the simple concept of a name. So we call it a section type.
    -- uses SAFE INNODB Intention Locks to support concurrency
    DECLARE valToUse INT;

    START TRANSACTION;
    SELECT nextSequence into valToUse from sequences where sectionType=p_sectionType FOR UPDATE;
    IF valToUse is null THEN
        SET valToUse=-1;
    END IF;
    UPDATE sequences set nextSequence=nextSequence+1 where sectionType=p_sectionType;
    COMMIT; -- get it and release INTENTION LOCK ASAP
    SELECT valToUse as yourSeqNum; -- return as a 1 column, 1 row resultset
END$$
DELIMITER ;
-- ****************************************************************************************
-- test:
call uspGetNextSequence('devices'); -- your section is 'devices'

Después de llamar a uspGetNextSequence(), es su RESPONSABILIDAD asegurarse de que esa secuencia #

se agrega a myTable (confirmándolo), o que si falla, lo insertes en

la tabla de reutilización con una llamada a uspAddToReuseList(). No todas las inserciones tienen éxito. Concéntrate en esta parte.

Porque con este código no puedes "ponerlo" de nuevo en las sequences mesa debido a

simultaneidad, otros usuarios y el rango que ya pasó. Entonces, simplemente, si la inserción falla,

pon el número en reuseMe vía uspAddToReuseList()

...

Proceso almacenado:uspAddToReuseList:

DROP PROCEDURE IF EXISTS uspAddToReuseList;
DELIMITER $$
CREATE PROCEDURE uspAddToReuseList(p_reuseNum INT)
BEGIN
    -- a stored proc to insert a sequence num into the reuse list
    -- marks it available for reuse (a status column called `reused`)
    INSERT reuseMe(seqNum,reused) SELECT p_reuseNum,0; -- 0 means it is avail, 1 not
END$$
DELIMITER ;
-- ****************************************************************************************
-- test:
call uspAddToReuseList(701); -- 701 needs to be reused

Proceso almacenado:uspGetOneToReuse:

DROP PROCEDURE IF EXISTS uspGetOneToReuse;
DELIMITER $$
CREATE PROCEDURE uspGetOneToReuse()
BEGIN
    -- a stored proc to get an available sequence num for reuse
    -- a return of -1 means there aren't any
    -- the slot will be marked as reused, the row will remain
    DECLARE retNum int; -- the seq number to return, to reuse, -1 means there isn't one

    START TRANSACTION;

    -- it is important that 0 or 1 rows hit the following condition
    -- also note that FOR UPDATE is the innodb Intention Lock
    -- The lock is for concurrency (multiple users at once)
    SELECT seqNum INTO retNum 
    FROM reuseMe WHERE reused=0 ORDER BY seqNum LIMIT 1 FOR UPDATE;

    IF retNum is null THEN
        SET retNum=-1;
    ELSE 
        UPDATE reuseMe SET reused=1 WHERE seqNum=retNum; -- slot used
    END IF;
    COMMIT; -- release INTENTION LOCK ASAP

    SELECT retNum as yoursToReuse; -- >0 or -1 means there is none
END$$
DELIMITER ;
-- ****************************************************************************************
-- test:
call uspGetOneToReuse();

Proceso almacenado:uspCleanReuseList:

DROP PROCEDURE IF EXISTS uspCleanReuseList;
DELIMITER $$
CREATE PROCEDURE uspCleanReuseList()
BEGIN
    -- a stored proc to remove rows that have been successfully reused
    DELETE FROM reuseMe where reused=1;
END$$
DELIMITER ;
-- ****************************************************************************************
-- test:
call uspCleanReuseList();

Proceso almacenado:uspOoopsResetToAvail:

DROP PROCEDURE IF EXISTS uspOoopsResetToAvail;
DELIMITER $$
CREATE PROCEDURE uspOoopsResetToAvail(p_reuseNum INT)
BEGIN
    -- a stored proc to deal with a reuse attempt (sent back to you)
    -- that you need to reset the number as still available, 
    -- perhaps because of a failed INSERT when trying to reuse it
    UPDATE reuseMe SET reused=0 WHERE seqNum=p_reuseNum;
END$$
DELIMITER ;
-- ****************************************************************************************
-- test:
call uspOoopsResetToAvail(701);

Ideas de flujo de trabajo:

Deje que GNS significa una llamada a uspGetNextSequence() .

Deja que RS media Secuencia de reutilización a través de una llamada a uspGetOneToReuse()

Cuando un nuevo INSERT se desea, llame a RS :

A. Si RS devuelve -1, entonces no se debe reutilizar nada, así que llame a GNS que devuelve N. Si puede INSERT con éxito con myTable.seqNum=N con una confirmación, ya está. Si no puede INSERT con éxito luego llame a uspAddToReuseList(N) .

B. Si RS devuelve> 0, tenga en cuenta que la ranura tiene reuseMe.reused=1 , una buena cosa para recordar. Por lo tanto, se supone que está en proceso de ser reutilizado con éxito. Llamemos a ese número de secuencia N. Si puede INSERT con éxito con myTable.seqNum=N con una confirmación, ya está. Si no puede INSERT con éxito luego llame a uspOoopsResetToAvail(N) .

Cuando considere seguro llamar a uspCleanReuseList() hazlo Agregando un DATETIME al reuseMe tabla puede ser una buena idea, indicando cuando una fila de myTable originalmente estaba eliminando y causando el reuseMe fila para obtener su INSERT original .