Monitoring and maintaining an int IDENTITY column

When using an int as an identity column in Microsoft SQL Server you’re bound to an upper limit of 2,147,483,647. Ordinarily, that limit would be extremely difficult to exceed e.g. if you had a ShoppingCarts table and it had an int IDENTITY field as the primary key and there were 10,000 carts created a day it would take 588 years to reach this limit.

But if for some reason you do reach this limit you will get error: “Arithmetic overflow error converting IDENTITY to data type int.

 

Lets setup some test structures and data:

CREATE TABLE [dbo].[ShoppingCarts](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserId] [int] NOT NULL,
    [Name] nvarchar(100),
 CONSTRAINT [PK_dbo.ShoppingCarts] PRIMARY KEY CLUSTERED ([Id] ASC))
GO

CREATE TABLE [dbo].[ShoppingCartItems](
    [ShoppingCartId] [int] NOT NULL,
    [ProductId] [int] NOT NULL,
    [Quantity] [int] NOT NULL
 CONSTRAINT [PK_dbo.ShoppingCartItems] PRIMARY KEY CLUSTERED ([ShoppingCartId] ASC, [ProductId] ASC))
GO

ALTER TABLE [dbo].[ShoppingCartItems] ADD  CONSTRAINT [FK_dbo.ShoppingCartItems_dbo.ShoppingCarts_ShoppingCartId] FOREIGN KEY([ShoppingCartId])
REFERENCES [dbo].[ShoppingCarts] ([Id])
GO

-- Reseed ShoppingCarts table so next inserted IDENTITY is 2147383648
DECLARE @Val int = 2147383647
DBCC CHECKIDENT ('ShoppingCarts', RESEED, @Val)

-- Attempts 100,001 inserts, 100,000 are successful but the last insert fails 
-- with the 'Arithmetic overflow error converting IDENTITY to data type int.'
WHILE 1=1
BEGIN
	INSERT INTO [dbo].[ShoppingCarts] ([UserId], [Name]) VALUES (1, 'Name')
	INSERT INTO [dbo].[ShoppingCartItems] ([ShoppingCartId], [ProductId], [Quantity]) VALUES (SCOPE_IDENTITY(), 1, 1)

	SET @Val = @Val + 1
END 

 

Now lets run through some code that can be used if you run into the “Arithmetic overflow error converting IDENTITY to data type int.” error. In short, the below code will reorganise the IDENTITY column to allow further records to be added.

-- Disable all constraints in the database.
EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

-- Copy records to temp table with new ids.
SELECT *,  rank() over (order by Id) as NewId
INTO #TempShoppingCarts
FROM [dbo].[ShoppingCarts]

-- Update linked tables with new id values.
UPDATE sci
SET sci.ShoppingCartId = tsc.NewId
FROM [dbo].[ShoppingCartItems] sci
INNER JOIN #TempShoppingCarts tsc
ON sci.ShoppingCartId = tsc.Id

-- Delete old records (we can't update an IDENTITY column).
DELETE FROM [dbo].[ShoppingCarts]

-- Allow inserting of IDENTITY field by insert statements (not allowed by default).
SET IDENTITY_INSERT [dbo].[ShoppingCarts] ON

-- Copy records back to orginal table with new ids. 
INSERT INTO [dbo].[ShoppingCarts] ([Id], [UserId], [Name])
SELECT [NewId], [UserId], [Name]
FROM #TempShoppingCarts

-- Reseed to the current max id
DECLARE @Max_Identity int 
SELECT @Max_Identity = MAX([Id]) FROM [dbo].[ShoppingCarts]
DBCC CHECKIDENT ('ShoppingCarts', RESEED, @Max_Identity)

-- Clean up
SET IDENTITY_INSERT [dbo].[ShoppingCarts] OFF
EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"
DROP TABLE #TempShoppingCarts

 

Run this to check what the current IDENEITY value is for all tables in the database with an IDENTITY column. If you are concerned about hitting the limit it would be a good idea to turn the below query into a scheduled job that alerted you when an IDENTITY column in the database reaches a threshold.

SELECT IDENT_SEED(TABLE_NAME) AS Seed
	,IDENT_INCR(TABLE_NAME) AS Increment
	,IDENT_CURRENT(TABLE_NAME) AS Current_Identity
	,TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE OBJECTPROPERTY(OBJECT_ID(TABLE_NAME), 'TableHasIdentity') = 1
AND TABLE_TYPE = 'BASE TABLE'
Advertisements