We can see three problems with the Identity property. SQL 6.5 had all three, SQL 7.0 two of them; SQL 2000 still has the last one.

1. The value of the next number was often lost and set to null. This led to problems with PK, which is often on the Identity column. We had to use DBCC CHECKIDENT. The problem was solved with SQL 7.0.

2. If the table has some triggers that insert into other tables with columns with Identity property, what value holds the @@identity global variable? From the last trigger, but we don’t know the order of the triggers? This problem was solved in SQL 2000 with the SCOPE_IDENTITY() and IDENT_CURRENT() functions. Besides that we have limited influence on triggers order – we can select the first and the last to fire.

3. The third problem is still not resolved in SQL 2000. The transaction isolation level does not have any influence on the Identity. Even with the serializable level you get gaps if a transaction is not committed.

The script bellow shows the third problem and the solution with manual sequential numbers. Please do check Joe Celko’s books and posts in public newsgroups for some other solutions as well.

 

/* Problems with Identity property

Manually inserting sequence numbers without gaps */

 

/* 1. Infrastructure */

IF OBJECT_ID(‘dbo.midents’,‘U’) IS NOT NULL

   DROP TABLE dbo.midents

GO

IF OBJECT_ID(‘dbo.demo1’,‘U’) IS NOT NULL

   DROP TABLE dbo.demo1

GO

IF OBJECT_ID(‘dbo.demo2’,‘U’) IS NOT NULL

   DROP TABLE dbo.demo2

GO

CREATE TABLE dbo.midents

(tablename varchar(10) PRIMARY KEY,

 mident int)

GO

CREATE TABLE dbo.demo1

(id int IDENTITY(1,1) NOT NULL)

GO

CREATE TABLE dbo.demo2

(id int NOT NULL)

GO

INSERT INTO dbo.midents VALUES(‘dbo.demo2’,0)

SELECT *

  FROM dbo.midents

GO

 

/* 2. Identity – can’t control transaction isolation level */

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

GO

BEGIN TRANSACTION

INSERT INTO dbo.demo1 DEFAULT VALUES

SELECT *

  FROM dbo.demo1

— stop here, open another session and

— execute statements from the point Session2

ROLLBACK TRANSACTION

 

— Session2

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

GO

 

BEGIN TRANSACTION

INSERT INTO dbo.demo1 DEFAULT VALUES

SELECT *

  FROM dbo.demo1

— blocked because of serializable isolation level

— go to session 1 and execute the rollback

COMMIT

— after rollback this transaction executes with the value 2

— serializable level did not affect the identity

— reset the isolation level

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

GO

 

— Session 1 again

— Check for forgotten transactions

SELECT @@trancount

— reset the isolation level

SET TRANSACTION ISOLATION LEVEL READ COMMITTED

GO

 

/* 3. Manual Identity – take control */

BEGIN TRANSACTION

DECLARE @mid int

SET @mid=0

UPDATE dbo.midents — WITH (HOLDLOCK)

   SET @mid=mident=mident+1

 WHERE tablename=‘dbo.demo2’

SELECT *

  FROM dbo.midents

SELECT @mid

INSERT INTO dbo.demo2 VALUES(@mid)

SELECT *

  FROM dbo.demo2

— stop here, open another session and execute statements from the point Session3

ROLLBACK TRANSACTION

 

— Session3

EXEC sp_lock

SELECT OBJECT_NAME(290100074),OBJECT_NAME(322100188) 

— find correct object id’s

— go to session 2 and execute statements from the point Session2

 

— Session2

BEGIN TRANSACTION 

— session 2 is waiting, show locks from session 3 again

DECLARE @mid int

SET @mid=0

UPDATE dbo.midents — WITH (HOLDLOCK)

   SET @mid=mident=mident+1

 WHERE tablename=‘dbo.demo2’

SELECT *

  FROM dbo.midents

SELECT @mid

INSERT INTO dbo.demo2 VALUES(@mid)

SELECT *

  FROM dbo.demo2

COMMIT

— go to session 1 and execute the rollback

— we have sequential numbers without gaps – QED

 

— Check for forgotten transactions

SELECT @@trancount