Probleme mit erster procedure

Tropaion

Neuer Benutzer
Beiträge
4
Guten Tag,
ich bin relativ neu mit MySQL und versuche gerade eine procedure zu erstellen, welche garantiert das eine eindeutige Seriennummer erstellt wird, wenn mehrere Clienten die procedure gleichzeitig aufrufen.
Die Datenbank hat nur einen Wert, welcher Primary ist und somit nur einmal vorkommen soll.
Mein Ansatz wäre, wenn bei INSERT ein duplicate error auftritt, wird die schleife noch einmal durchgelaufen.
Anhand des @@ROWCOUNTS wollte ich erkennen, ob INSERT erfolgreich war, jedoch hab ich bemerkt, das dies von MySQL nicht unterstützt wird und jetzt bin ich ratlos, wie sich das lösen lässt. Ich hoffe jemand kann mir dabei helfen.
Code:
-- Delete this procedure if it already exists to overwrite it with the new one
DROP PROCEDURE IF EXISTS `GenerateUniqueSerialNumber`;

-- Change delimiter to // so that procedure is handled as one statement
DELIMITER //

-- Create procedure for serial number generation
CREATE PROCEDURE GenerateUniqueSerialNumber(IN article_number VARCHAR(6), OUT serial_number VARCHAR(16))
BEGIN
    -- Counter variable for serial number in numerical and string form
    DECLARE serial_counter_num INT DEFAULT 1;
    DECLARE serial_counter_str VARCHAR(4) DEFAULT '0001';

    -- Continue procedure if duplicate error occurs
    DECLARE CONTINUE HANDLER FOR 1062

    DECLARE retry_count INT DEFAULT 0;
    DECLARE max_retries INT DEFAULT 10; -- You can adjust the maximum number of retries as needed

    -- Retry loop to handle concurrency
    RETRY_BLOCK: LOOP
        -- Increment retry count
        SET retry_count = retry_count + 1;

        -- Check if a serial number for current date already exists
        IF EXISTS (SELECT * FROM serials WHERE serial LIKE CONCAT(article_number, date_format(now(), '%y%m%d'), '%')) THEN
            -- Search table for latest entry for given article number and current date
            SET serial_number = SELECT MAX(SERIAL) FROM serials WHERE serial LIKE CONCAT(article_number, date_format(now(), '%y%m%d'), '%');
            -- Get the last four chars from the serial number
            SET serial_counter_str = RIGHT(serial_number, 4);
            -- Convert to int
            SET serial_counter_num = CAST(serial_counter_str AS UNSIGNED);
            -- Inkrement serial counter
            SET serial_counter_num = serial_counter_num + 1;

            -- Check if serial counter is within bounds (1 - 9999)
            IF serial_counter_num > 9999 THEN
                SET serial_counter_num = 1;
            END IF;

            -- Convert to string with four digits (leading zeros)
            SET serial_counter_str = LPAD(serial_counter_num, 4, '0');
        END IF;

        -- Create new serial number
        SET serial_number = CONCAT(article_number, date_format(now(), '%y%m%d'), serial_counter_str);

        -- Insert incremented serial number into database and ignore execution errors
        INSERT IGNORE INTO serials(serial) VALUES (serial_number);

        -- Affected rows should be >0 if commit was successful
        IF @@ROWCOUNT > 0 THEN
            -- Exit loop
            LEAVE RETRY_BLOCK;
        END IF;

        -- Check if the maximum retry count is reached
        IF retry_count >= max_retries THEN
            SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = 'Failed to generate unique serial number after maximum retries';
        END IF;

        -- Wait for a short period before retrying (you may need to adjust the sleep time)
        DO SLEEP(0.1);
    
    END LOOP RETRY_BLOCK;

-- And of procedure
END //

-- Reset delimiter to default
DELIMITER ;

-- Call function
CALL GenerateUniqueSerialNumber('123456');
SELECT * FROM GenerateUniqueSerialNumber();
Des weiteren gibt die Zeile ```
SELECT MAX(SERIAL) FROM serials WHERE serial LIKE CONCAT(article_number, date_format(now(), '%y%m%d'), '%');``` einen Syntax-Error aus, obwohl ich denn Abruf selbst getested habe und dieser funktioniert.

Ich wäre dankbar wenn mir jemand helfen könnte.
 
Werbung:
Wie gesagt, ich bin relativ neu mit MySQL.
Wieso sollte es in einer Multiuser-Umgebung nicht funktioniert?
INSERT ist atomic und in der DB ist serial ein Primary-Key -> Duplikate sind nicht erlaubt, also verwende ich indirekt eine UNIQUE-Spalte. Wenn ein Duplikat-Fehler auftritt, wird dieser abgefangen.
auto_increment kann ich nicht verwenden, da serial ein bestimmtes Muster enspricht, aus einer 6-stelligen Nummer, einem 6-stelligem Datum und einem 4-stelligem inkrementierenden Wert, also fällt SERIAL und UUID weg.
 
Was für eine Lösung würdest du dann Vorschlagen? Es werden zwei User sein welche alle paar Minuten eine Anfrage stellen werden, Kollisionen nicht unmöglich aber sehr unwahrscheinlich.
 
Was für eine Lösung würdest du dann Vorschlagen?
Ich denke, dass Du da 2 Dinge vermischt, die nichts miteinander zu tun haben.

Primärschlüssel, die ein Datenmodell nun mal braucht, sind etwas anderes als (fachliche) Seriennummer.
Ein Primärschlüssel hat in einem Datenmodell eine einzige Aufgabe: Eindeutigkeit (zu garantieren!)
Eine Seriennummer bzw. jegliches andere fachliche Datum (data) sollte niemals gleichzeitig ein Primärschlüssel sein.

Was bedeutet das für Dich?
Du definierst in Deinen Tabellen ganz normale Primärschlüssel wie es so üblich ist, mit den Mitteln, die Deine DB dafür so vorsieht.
Damit kannst Du nun Datensätze anlegen so viel wie es geht, mit vielen Nutzern, alles gut.

Da wo Du eine Seriennummer erzeugen musst, verwendest einen weiteren Mechanismus, der dem benötigten Muster folgt und Eindeutigkeit garantiert. Formal erreichst Du die Eindeutigkeit mit den gleichen Mitteln wie bei einem Primärschlüssel. Du definierst einen Unique Constraint und spendierst einen passenden Index (soweit das nicht automatisch erfolgt).

Das bedeutet jetzt
a) Die Datensätz, die Du anlegst, haben immer einen funktionierenden Primärschlüssel, können also gespeichert und geändert und referenziert werden
b) Es wird immer eine eindeutige Seriennummer geben, garantiert, weil Du es so definiert hast und nichts anderes gespeichert werden darf.
c) Wenn der Algorithmus, den Du zur Definition einer eindeutigen Seriennummer vorsiehst, einmal fehlschlägt, passiert nichts schlimmes.
 
Außerdem baust du da einen Counter auf eine merkwürdige Weise. Die ganz banale Lösung dafür ist den letzten Counter-Wert in einer eigenen Tabelle zu speichern und beim Einfügen neuer Datensätze ebenfalls hoch zu zählen. Nicht sehr elegant aber funktioniert immer. Das Einfügen neuer Datensätze und das hoch zählen des Counters muss nur in der selben Transaktion statt finden und schon hast du Sicherheit.
 
Werbung:
Außerdem baust du da einen Counter auf eine merkwürdige Weise. Die ganz banale Lösung dafür ist den letzten Counter-Wert in einer eigenen Tabelle zu speichern und beim Einfügen neuer Datensätze ebenfalls hoch zu zählen. Nicht sehr elegant aber funktioniert immer. Das Einfügen neuer Datensätze und das hoch zählen des Counters muss nur in der selben Transaktion statt finden und schon hast du Sicherheit.
naja, man sollte da wohl noch einen LOCK setzen, aber egal ...
 
Zurück
Oben