sexta-feira, 22 de abril de 2016

SQL Server 2012 e 2014 - Pulando (saltando) sequencia de autoincremento (identity)


A partir do SQL Server 2012 notei existe um bug na sequencia de autoincremrnto dos campos IDENTITY. Notei isso em 25/03/2012, ou seja, logo que lançaram o 2012. Postei no MSDN a questão e mesmo a confirmação do bug por parte de usuários experientes e certificados, pediram pra aguardar que muito possivelmente viria uma correção (ver post).

Mas veio o 2012 SP1 e o 2014 e o problema continua. Exemplo de um caso já no MSSQL 2014, onde o identity salta do 9 para o 1009:

Com o intuito de ajudar os que passam pelo mesmo problema e ainda aguardam uma solução por parte da Microsoft, vou postar uma solução paliativa (gambiarra) para evitar que o salto aconteça.

Basta executar a query abaixo e não haverá mais saltos do identity em todo o servidor (todos os bancos e tabelas, mesmo os criados depois).

Caso prefira troque o nome da Stored Procedure de sp_FixSeeds2012 para sp_FixSeeds[e o numero da sua versao], mas não interfere no funcionamento. Portanto aconselho não perder tempo com isso.

USE master; 
GO
CREATE PROCEDURE sp_FixSeeds2012
AS
BEGIN

    --foreach database
    DECLARE @DatabaseName varchar(255)
    
    DECLARE DatabasesCursor CURSOR READ_ONLY
    FOR
        SELECT name
        FROM sys.databases
        where name not in ('master','tempdb','model','msdb') and sys.databases.state_desc = 'online'

    OPEN DatabasesCursor

    FETCH NEXT FROM DatabasesCursor
    INTO @DatabaseName

    WHILE @@FETCH_STATUS = 0
    BEGIN
    
        EXEC ('USE '+@DatabaseName + '

        --foreach identity column
        DECLARE @tableName varchar(255)
        DECLARE @columnName varchar(255)
        DECLARE @schemaName varchar(255)
    
        DECLARE IdentityColumnCursor CURSOR READ_ONLY
        FOR
        
            select TABLE_NAME , COLUMN_NAME, TABLE_SCHEMA 
            from INFORMATION_SCHEMA.COLUMNS 
            where COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, ''IsIdentity'') = 1 
        

        OPEN IdentityColumnCursor

        FETCH NEXT FROM IdentityColumnCursor
        INTO @tableName, @columnName, @schemaName

        WHILE @@FETCH_STATUS = 0
        BEGIN
        
            print ''['+@DatabaseName+'].[''+@tableName+''].[''+@schemaName+''].[''+@columnName+'']'' 
            EXEC (''declare @MAX int = 0
                    select @MAX = max(''+@columnName+'') from ['+@DatabaseName+'].[''+@schemaName+''].[''+@tableName+'']
                    if (@MAX IS NULL)
                    BEGIN
                        SET @MAX = 0
                    END
                    DBCC CHECKIDENT(['+@DatabaseName+'.''+@schemaName+''.''+@tableName+''],RESEED,@MAX)'')

            FETCH NEXT FROM IdentityColumnCursor
            INTO @tableName, @columnName, @schemaName

        END

        CLOSE IdentityColumnCursor
        DEALLOCATE IdentityColumnCursor')



        FETCH NEXT FROM DatabasesCursor
        INTO @DatabaseName

    END

    CLOSE DatabasesCursor
    DEALLOCATE DatabasesCursor
END
GO

EXEC sp_configure 'show advanced options', 1 ;
GO
RECONFIGURE
GO
EXEC sp_configure 'scan for startup procs', 1 ;
GO
RECONFIGURE
GO



EXEC sp_procoption @ProcName = 'sp_FixSeeds2012' 
    , @OptionName = 'startup' 
    , @OptionValue = 'true' 
GO

3 comentários:

  1. Acho que assim fica bem melhor,

    USE [master]
    GO
    /****** Object: StoredProcedure [dbo].[sp_FixSeeds2012] Script Date: 20/12/2019 17:12:20 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    ALTER PROCEDURE [dbo].[sp_FixSeeds2012]
    AS
    BEGIN
    DECLARE @txQUERY VARCHAR(MAX) = NULL
    SET @txQUERY = CONCAT('USE [master];', CHAR(13), CHAR(10),'DECLARE @varTOTAL INT = 0;')

    ;WITH tmpDATABASE AS (
    SELECT
    [NAME] AS 'db_NAME'
    FROM [sys].[databases]
    WHERE name not in ('master','tempdb','model','msdb')
    AND [sys].[databases].[state_desc] = 'online'
    ), tmpTABELA AS (
    SELECT
    a.TABLE_NAME AS 'tab_NAME',
    a.COLUMN_NAME AS 'tab_COLUNA',
    a.TABLE_SCHEMA AS 'tab_SCHEMA',
    a.TABLE_CATALOG AS 'tab_DATABASE'
    FROM [INFORMATION_SCHEMA].[COLUMNS] a
    JOIN [SYSOBJECTS] b ON a.TABLE_NAME = b.NAME
    WHERE (COLUMNPROPERTY(object_id(a.TABLE_NAME), a.COLUMN_NAME, 'IsIdentity') = 1)
    AND b.TYPE = 'U'
    )
    SELECT
    @txQUERY = CONCAT(
    @txQUERY,
    CHAR(13), CHAR(10), CHAR(13), CHAR(10),
    'USE ', [db_NAME], ';',
    CHAR(13), CHAR(10),
    'SET @varTOTAL = ISNULL((SELECT MAX([',
    [tab_COLUNA],
    ']) FROM [',
    [tab_SCHEMA],
    '].[',
    [tab_NAME],
    ']), 0);',
    CHAR(13), CHAR(10),
    'PRINT ''++++++++++++++++++''',
    CHAR(13), CHAR(10),
    'PRINT ''[', [db_NAME],'].[', [tab_SCHEMA], '].[', [tab_NAME], ']''',
    CHAR(13), CHAR(10),
    'IF (@varTOTAL > 0) DBCC CHECKIDENT(''[',
    [db_NAME],
    '].[',
    [tab_SCHEMA],
    '].[',
    [tab_NAME],
    ']'',''RESEED'', @varTOTAL);'
    )
    FROM tmpDATABASE a
    JOIN tmpTABELA b ON a.db_NAME = b.tab_DATABASE
    ORDER BY [db_NAME], [tab_NAME]

    -- SELECT @txQUERY AS '@txQUERY'
    -- PRINT '@txQUERY=' + @txQUERY
    EXEC(@txQUERY)
    --
    RETURN
    END

    GO

    ResponderExcluir
  2. Bom dia Amigo,
    Achei interessante seu código, acredito que vai resolver meu problema aqui, muito obrigado!

    ResponderExcluir
  3. Na verdade isso é um comportamento normal do SQL Server, em um transação que possua insert, um cache de identity por default reserva 1000 ids. Não me recordo agora com certeza, mas acho que isso é por uma questão de melhor performance ao realizar muitos inserts em uma mesma tabela em uma transação.

    Ocorre que se o serviço do MSSQL parar no meio de uma transação, que estava inserindo registros, sem passar pelo roolback ou commit, em uma eventual queda de energia, ou parada forçada do serviço, falha na rede, por exemplo, você vai notar esse "salto" de 1000 ids.

    A nível de banco de dados, a partir da versão 2017 esse comportamento pode ser desativado com o comando:

    ALTER DATABASE SCOPED CONFIGURATION SET IDENTITY_CACHE = OFF;

    Existem outras formas de fazer isso via Trace Flags na inicialização do servidor também, mas acredito que essa a nível de banco seja a mais interessante.

    ResponderExcluir

Oracle - Listar datas do mês

 select TRUNC(SYSDATE)  + level - 1 dt from   dual connect by level <= (   LAST_DAY(SYSDATE) - TRUNC(SYSDATE) + 1 )