Mysql
 sql >> Base de données >  >> RDS >> Mysql

Champ de lignes séquentielles contiguës MySQL même lors de la suppression et de l'insertion

Je sais qu'il y en a beaucoup ici. J'ai essayé de le documenter assez bien à l'intérieur du code et ici et là. Il utilise des procédures stockées. Vous pouvez naturellement extraire le code et ne pas utiliser cette méthode. Il utilise une table principale qui héberge les prochains incrémenteurs disponibles. Il utilise le coffre-fort INNODB Intention Locks pour la simultanéité. Il a une table de réutilisation et des procédures stockées pour le prendre en charge.

Il n'utilise en aucun cas la table myTable . Il y est montré pour votre propre imagination en fonction des commentaires sous votre question. Le résumé de cela est que vous savez que vous aurez des lacunes sur DELETE . Vous voulez une manière ordonnée de réutiliser ces créneaux, ces numéros de séquence. Ainsi, lorsque vous DELETE une ligne, utilisez les procs stockés en conséquence pour ajouter ce nombre. Naturellement, il existe un proc stocké pour obtenir le numéro de séquence suivant pour la réutilisation et d'autres choses.

À des fins de test, votre sectionType ='appareils'

Et le meilleur de tout, c'est qu'il est testé !

Schéma :

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

Procédure stockée :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'

Après avoir appelé uspGetNextSequence(), il est de votre RESPONSABILITÉ de vous assurer que cette séquence #

est soit ajouté dans myTable (en le confirmant), ou que s'il échoue, vous l'insérez dans

la table de réutilisation avec un appel à uspAddToReuseList(). Toutes les insertions ne réussissent pas. Concentrez-vous sur cette partie.

Parce qu'avec ce code vous ne pouvez pas le "remettre" dans les sequences table à cause de

la concurrence, les autres utilisateurs et la gamme déjà dépassée. Donc, simplement, si l'insertion échoue,

mettre le numéro dans reuseMe via uspAddToReuseList()

...

Procédure stockée :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

Procédure stockée :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();

Procédure stockée :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();

Procédure stockée :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);

Idées de workflow :

Laissez GNS signifie un appel à uspGetNextSequence() .

Soit RS signifie réutiliser la séquence via un appel à uspGetOneToReuse()

Lorsqu'un nouveau INSERT est souhaité, appelez RS :

A. Si RS renvoie -1 alors rien ne doit être réutilisé donc appelez GNS qui renvoie N. Si vous pouvez réussir INSERT avec myTable.seqNum=N avec une confirmation, vous avez terminé. Si vous ne parvenez pas à INSERT puis appelez uspAddToReuseList(N) .

B. Si RS renvoie> 0, notez dans votre tête que l'emplacement a reuseMe.reused=1 , une bonne chose à retenir. Il est donc supposé être en train d'être réutilisé avec succès. Appelons ce numéro de séquence N. Si vous pouvez réussir INSERT avec myTable.seqNum=N avec une confirmation, vous avez terminé. Si vous ne parvenez pas à INSERT puis appelez uspOoopsResetToAvail(N) .

Lorsque vous jugez qu'il est sûr d'appeler uspCleanReuseList() faites-le. Ajouter un DATETIME au reuseMe table peut être une bonne idée, indiquant quand une ligne de myTable supprimait à l'origine et provoquait le reuseMe ligne pour obtenir son INSERT d'origine .