sql >> Databasteknik >  >> RDS >> Mysql

MySQL Contiguous Sequential Rows Field även vid radering och infogning

Jag vet att det finns mycket här. Jag försökte dokumentera det ganska bra i koden och här och där. Den använder lagrade procedurer. Du kan naturligtvis dra ut koden och inte använda den metoden. Den använder en huvudtabell som innehåller nästa tillgängliga inkrementorer. Den använder säker INNODB Avsiktslås för samtidighet. Den har en återanvändningstabell och lagrade processer för att stödja den.

Den använder inte på något sätt tabellen myTable . Det visas där för din egen fantasi baserat på kommentarer under din fråga. Sammanfattningen av det är att du vet att du kommer att ha luckor vid DELETE . Du vill ha ett ordnat sätt för att återanvända de där slotsen, de där sekvensnumren. Så när du DELETE en rad, använd de lagrade processerna för att lägga till det numret. Naturligtvis finns det en lagrad proc för att få nästa sekvensnummer för återanvändning och annat.

För teständamål, din sectionType ='enheter'

Och det bästa av allt är att den är testad!

Schema:

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

Lagrad proc: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'

Efter att du anropat uspGetNextSequence() är det ditt ANSVAR att se till att den sekvensen #

läggs antingen till i myTable (genom att bekräfta det), eller att om det misslyckas, infogar du det i

återanvändningstabellen med ett anrop till uspAddToReuseList(). Inte alla insatser lyckas. Fokusera på den här delen.

För med den här koden kan du inte "sätta" tillbaka den i sequences tabell på grund av

samtidighet, andra användare och intervallet som redan passerat. Så, helt enkelt, om infogningen misslyckas,

lägg in numret i reuseMe via uspAddToReuseList()

...

Lagrad proc: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

Lagrad proc: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();

Lagrad proc: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();

Lagrad proc: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);

Arbetsflödesidéer:

Låt GNS betyder ett anrop till uspGetNextSequence() .

Låt RS betyder Återanvändningssekvens via ett anrop till uspGetOneToReuse()

När en ny INSERT önskas, ring RS :

A. Om RS returnerar -1 så ska ingenting återanvändas så ring GNS som returnerar N. Om du lyckas INSERT med myTable.seqNum=N med en bekräftelse är du klar. Om du inte lyckas INSERT det, anrop sedan uspAddToReuseList(N) .

B. Om RS returnerar> 0, notera i ditt huvud att plats har reuseMe.reused=1 , en bra sak att komma ihåg. Så det antas vara i färd med att framgångsrikt återanvändas. Låt oss kalla det sekvensnumret N. Om du lyckas INSERT med myTable.seqNum=N med en bekräftelse är du klar. Om du inte lyckas INSERT det, anrop sedan uspOoopsResetToAvail(N) .

När du anser det säkert att anropa uspCleanReuseList() göra det. Lägger till en DATETIME till reuseMe tabell kan vara en bra idé, anger när en rad från myTable ursprungligen tog bort och orsakade reuseMe rad för att få dess ursprungliga INSERT .




  1. fråga om php summary 01 + 01 =02

  2. Hur kan jag tvinga värdet på en MySQL-fråga att använda en viss sortering?

  3. Hur dödar/stoppar man en lång SQL-fråga omedelbart?

  4. Hur man utvärderar uttryck i select-sats i Postgres