Impedir a redefinição da identificação de auto_increment no banco de dados Innodb após a reinicialização do servidor

11

Li recentemente que, devido ao modo como o InnoDB recalcula o valor AUTO_INCREMENT quando o servidor é reiniciado, qualquer registro na parte alta da lista de IDs pode ter seus IDs reutilizados.

Normalmente, isso não é um problema, porque quando um usuário é excluído, tudo associado ao ID também é excluído de outras tabelas.

Mas, deliberadamente, deixo as postagens no fórum órfãs, rotuladas como "Publicado por = Usuário nº 123 =", para que as conversas anteriores sejam mantidas. Claramente, se um ID for reutilizado, isso será um problema.

Eu nunca tive esse problema antes porque sempre havia novos usuários suficientes para tornar improvável a reutilização de um ID dessa maneira. No entanto, no meu novo projeto, as inscrições são raras e as exclusões de usuários inativas são frequentes (especialmente porque as contas "Open Alpha" duram apenas três dias como uma visualização), e essa reutilização de ID aconteceu três a três agora.

Eu "corrigi" o problema salvando o valor correto para AUTO_INCREMENT em outro lugar e usando-o em vez de confiar no valor interno. Existe uma maneira real de o InnoDB lembrar o último valor real?

Naveen Kumar
fonte
Você tem o artigo que leu?
GBN
@gbn O link para o artigo dev.mysql.com/doc/refman/5.1/en/…
Naveen Kumar
Para referência, isto é bugs.mysql.com/bug.php?id=199
Laurynas
ALTER TABLE table_name ENGINE = MyISAM Funciona para mim. Nossa tabela é sempre mantida muito pequena, portanto não há necessidade do InnoDB.
1
@QuickFix Você deve adicionar alguns detalhes sobre por que isso funciona.
Max Vernon

Respostas:

5

(evitando o problema nunca excluindo)

Como você deseja manter as "Posted by =User #123="informações depois de excluir o usuário id=123, também pode considerar o uso de 2 tabelas para armazenar dados dos usuários. Um para Activeusuários e um para todos (incluindo os excluídos dos usuários ativos). E nunca exclua esses IDs da AllUsertabela:

CREATE TABLE AllUser
( user_id INT AUTO_INCREMENT
, ...
, PRIMARY KEY (user_id)
) ;

------
--- Forum posts FK should reference the `AllUser` table

CREATE TABLE ActiveUser
( user_id INT 
, ...
, PRIMARY KEY (user_id)
, FOREIGN KEY (user_id)
    REFERENCES AllUser (user_id)
) ;

------
--- All other FKs should reference the `ActiveUser` table

Obviamente, isso complicará a operação de inserção de novo usuário. Qualquer novo usuário significa 2 inserções, uma em cada tabela. A exclusão de um usuário será excluída ActiveUserapenas da tabela. Todos os FKs serão excluídos em cascata, exceto as postagens do fórum, que farão referência à Allusertabela (onde nunca haverá exclusão).

ypercubeᵀᴹ
fonte
4

Não há maneira natural de fazer isso, exceto usar information_schema.tables para registrar todas as colunas com a opção auto_increment.

Você pode coletar essas colunas da seguinte maneira:

CREATE TABLE mysql.my_autoinc ENGINE=MyISAM
SELECT table_schema,table_name,auto_increment
FROM information_schema.tables WHERE 1=2;
ALTER TABLE mysql.my_autoinc ADD PRIMARY KEY (table_schema,table_name);
INSERT INTO mysql.my_autoinc
SELECT table_schema,table_name,auto_increment
FROM information_schema.tables WHERE auto_increment IS NOT NULL;

Crie um script que redefinirá os valores de incremento automático

AUTOINC_SCRIPT=/var/lib/mysql/ResetAutoInc.sql
mysql -u... -p... -AN -e"SELECT CONCAT('ALTER TABLE ',table_schema,'.',table_name,' AUTO_INCREMENT=',auto_increment,';') FROM mysql.my_autoinc" > ${AUTOINC_SCRIPT}

Você pode fazer uma de duas coisas:

OPÇÃO # 1: executar o script manualmente após a inicialização

mysql> source /var/lib/mysql/ResetAutoInc.sql

OPÇÃO # 2: Faça o mysqld executar o script antes de permitir conexões

Você precisaria adicionar esta opção

[mysqld]
init-file=/var/lib/mysql/ResetAutoInc.sql

Dessa forma, toda vez que você reinicia o mysql, esse script é executado no início. Você precisará se lembrar de regenerar /var/lib/mysql/ResetAutoInc.sql antes de fazer uma reinicialização planejada do mysql.

RolandoMySQLDBA
fonte
3

Os documentos 5.5 sugerem armazenar o valor de incremento automático em outro lugar, como você já possui.

Uma solução alternativa seria emular uma SEQUENCE para que você não utilize o incremento automático na própria tabela real. Isso foi discutido no SO antes e novamente . O blog MySQL Performance menciona isso.

Ainda outros dados do MySQL que outros RDBMS não têm ...

gbn
fonte
2

Apenas não exclua o usuário. A integridade relacional é mais importante. Se você precisar, por motivos de privacidade ou qualquer outra coisa, simplesmente altere o nome de usuário para 'excluído' e limpe quaisquer outros campos.

Jannes
fonte
1

Esta é uma pergunta antiga e ainda relevante.

1) Esse comportamento está sendo corrigido no Mysql 8.0.

2) Uma solução é usar uma linha fictícia para seus dados, para manter o AUTO_INCREMENT acima de um determinado valor. Não é super conveniente, dependendo do que você está armazenando, mas é uma solução simples em alguns casos.

Garr Godfrey
fonte
0

Precisávamos disso e extrapolamos uma solução para nosso próprio sistema, com base nas instruções contidas neste post. Se isso puder ajudar alguém a atingir seu objetivo de uma maneira ainda mais fácil.

Nosso sistema usa um padrão de tabela de marca para exclusão para armazenar itens excluídos, porque fazemos sincronização bidirecional em sistemas desconectados; portanto, usamos esse código para corresponder as tabelas de marca para exclusão com suas tabelas ativas e extrair o valor mais alto possível :)

DROP PROCEDURE IF EXISTS `reset_auto_increments`;
DELIMITER $
CREATE PROCEDURE reset_auto_increments()
BEGIN

    DECLARE done INT DEFAULT 0;
    DECLARE schemaName VARCHAR(255) DEFAULT '';
    DECLARE liveTableName VARCHAR(255) DEFAULT '';
    DECLARE tombstoneTableName VARCHAR(255) DEFAULT '';
    DECLARE liveAutoIncrement INT DEFAULT 0;
    DECLARE tombstoneAutoIncrement INT DEFAULT 0;
    DECLARE newAutoIncrement INT DEFAULT 0;

    DECLARE autoIncrementPairs CURSOR FOR 
        SELECT
            liveTables.TABLE_SCHEMA AS schemaName,
            liveTables.TABLE_NAME AS liveTable, 
            tombstoneTables.TABLE_NAME AS tombstoneTable,
            liveTables.AUTO_INCREMENT AS live_auto_increment,
            tombstoneTables.AUTO_INCREMENT AS tombstone_auto_increment,
            GREATEST(liveTables.AUTO_INCREMENT, tombstoneTables.AUTO_INCREMENT) AS new_auto_increment
        FROM 
            information_schema.tables AS liveTables
            JOIN information_schema.tables AS tombstoneTables
                ON liveTables.TABLE_SCHEMA = tombstoneTables.TABLE_SCHEMA
                    AND CONCAT('deleted', UCASE(LEFT(liveTables.TABLE_NAME, 1)), SUBSTRING(liveTables.TABLE_NAME, 2))
                        = tombstoneTables.TABLE_NAME
        WHERE
            GREATEST(liveTables.AUTO_INCREMENT, tombstoneTables.AUTO_INCREMENT) IS NOT NULL;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    SET done = 0;

    SET schemaName = '';
    SET liveTableName = '';
    SET tombstoneTableName = '';
    SET liveAutoIncrement = 0;
    SET tombstoneAutoIncrement = 0;
    SET newAutoIncrement = 0;

    OPEN autoIncrementPairs;
    REPEAT

        FETCH autoIncrementPairs INTO 
            schemaName, 
            liveTableName, 
            tombstoneTableName, 
            liveAutoIncrement, 
            tombstoneAutoIncrement, 
            newAutoIncrement;

        SET @statement = CONCAT('ALTER TABLE ', schemaName, '.', liveTableName, ' AUTO_INCREMENT=', newAutoIncrement);
        PREPARE updateAutoIncrementStatement FROM @statement;
        EXECUTE updateAutoIncrementStatement;
        DEALLOCATE PREPARE updateAutoIncrementStatement;

    UNTIL done END REPEAT;

    CLOSE autoIncrementPairs;

END$

DELIMITER ;
Mathieu Dumoulin
fonte