使用自动递增键插入的许多行的连续数字

时间:2016-03-31 18:00:02

标签: sql sql-server sql-server-2008-r2 auto-increment

  • SQL Server 2008 R2
  • 具有自动递增键的表
  • 许多不同的线程必须在表中批量插入行。

我想知道如何(如果可能的话)以一种方式插入行,以确保一个线程的插入行的键将获得序列号。

例如,如果两个线程同时执行:

  • 线程#1插入5行并获取密钥1,2,3,4,5
  • 线程#2插入5行并获取密钥6,7,8,9,10

我一定不要得到:

  • 线程#1行获取密钥1,3,4,8,9
  • 线程#2行获取密钥2,5,6,7,10

显然,如果每个线程执行某种循环并执行5次" INSERT INTO ..."命令它不会起作用,因为另一个线程可以插入。

但即使一个线程只使用一个INSERT命令来插入很多行,是否足以保证密钥是顺序的?

如果是的话,你能帮我找一下它的记录吗?因为我没有。

如果不是,怎么可能确保?

编辑为什么我要关注连续数字:

它主要是一个性能问题,我们实际上有一个整数不是自动递增列,每个线程锁定该行,手动增加列值所需的任何数字,而不是释放行。

问题在于,一次只能插入一个线程,通过测试我们发现在新表自动增量列中插入行,让sql server管理身份分配,要快得多。 锁定整个表不是一个选项,因为它会导致与锁定公共行相同的问题。

我想确保插入的行id对于单个线程插入是顺序的原因是为了减少代码重构的需要,这实际上通过仅保留第一个数字和计数来工作,因此代码可以推断出什么是其他数字。

将数字作为连续数并不是一个商业问题,所以如果不可能,我们只需要将每个行号保留在一个数组中,但是有更多的代码可以用这种方式进行重构,所以我'我试图尽可能避免它。

请记住,我完全清楚设计可能并不理想,但我正在使用传统的“泥球”#34;我无法重新设计的系统。

4 个答案:

答案 0 :(得分:1)

使用交易。 事务将锁定表,直到您将提交,因此在上一个事务结束之前不会启动任何其他事务,因此身份值是安全的

答案 1 :(得分:1)

我很好奇地测试。在我的SQL Server 2014 Express虚拟机上,答案是:

当多个线程插入值时,生成的IDENTITY保证是顺序的。即使它是一个INSERT语句,一次插入多行。 (在默认事务隔离级别下)

您可以在SQL Server 2008上进行测试,但即使您没有看到相同的行为,依靠它也不明智,因为它在2014年肯定发生了变化。

以下是重现测试的完整脚本。

表格

CREATE TABLE [dbo].[test](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [dt] [datetime2](7) NOT NULL,
    [V] [int] NOT NULL,
 CONSTRAINT [PK_test] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

INSERT脚本

WAITFOR TIME '22:23:24';
-- set the time to about a minute in the future
-- open two windows in SSMS and run this script (F5) in both of them
-- they will start running at the same time specified above in parallel.

-- insert 1M rows in chunks of 1000 rows

-- in the first SSMS window uncomment these lines:
--DECLARE @VarV int = 0;
--WHILE (@VarV < 1000)

-- in the second SSMS window uncomment these lines:
--DECLARE @VarV int = 10000;
--WHILE (@VarV < 11000)

BEGIN

    WITH e1(n) AS
    (
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
        SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
    ) -- 10
    ,e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b)    -- 10*10
    ,e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2)         -- 10*100
    ,CTE_rn
    AS
    (
        SELECT ROW_NUMBER() OVER (ORDER BY n) AS rn
        FROM e3
    )
    INSERT INTO [dbo].[test]
        ([dt]
        ,[V])
    SELECT
        SYSDATETIME() AS dt
        ,@VarV
    FROM CTE_rn;

    SET @VarV = @VarV + 1;

END;

验证结果

WITH
CTE
AS
(
    SELECT 
        [V]
        ,MIN(ID) AS MinID
        ,MAX(ID) AS MaxID
        ,MAX(ID) - MIN(ID) + 1 AS DiffID
    FROM [dbo].[test]
    GROUP BY V
)
SELECT
    DiffID
    ,COUNT(*) AS c
FROM CTE
GROUP BY DiffID
ORDER BY c DESC;

此查询为每个MIN计算MAXID V(1000个插入行的每个块)。如果所有IDENTITY值都是按顺序生成的,则MAXMIN ID之间的差异将始终为1000.正如我们在结果中看到的那样,情况并非如此:

<强>结果

+--------+------+
| DiffID |  c   |
+--------+------+
|   1000 | 1940 |
|   2000 |    6 |
|   3000 |    3 |
|   1759 |    2 |
|   1477 |    2 |
|   1522 |    1 |
|   1524 |    1 |
|   1529 |    1 |
|   1538 |    1 |
|   1546 |    1 |
|   1548 |    1 |
|   1584 |    1 |
|   1585 |    1 |
|   1589 |    1 |
|   1597 |    1 |
|   1606 |    1 |
|   1611 |    1 |
|   1612 |    1 |
|   1620 |    1 |
|   1630 |    1 |
|   1631 |    1 |
|   1635 |    1 |
|   1658 |    1 |
|   1663 |    1 |
|   1675 |    1 |
|   1731 |    1 |
|   1749 |    1 |
|   1009 |    1 |
|   1038 |    1 |
|   1049 |    1 |
|   1055 |    1 |
|   1086 |    1 |
|   1102 |    1 |
|   1144 |    1 |
|   1218 |    1 |
|   1225 |    1 |
|   1263 |    1 |
|   1325 |    1 |
|   1367 |    1 |
|   1372 |    1 |
|   1415 |    1 |
|   1451 |    1 |
|   1761 |    1 |
|   1793 |    1 |
|   1832 |    1 |
|   1904 |    1 |
|   1919 |    1 |
|   1924 |    1 |
|   1954 |    1 |
|   1973 |    1 |
|   1984 |    1 |
|   2381 |    1 |
+--------+------+

在大多数情况下,确实IDENTITY值按顺序分配,但在2000年的60个案例中,它们不是。

如何处理?

我个人更喜欢使用sp_getapplock,而不是锁定表或增加事务隔离级别。

但是,最终结果是一样的 - 您必须确保INSERT语句不是并行运行。

在SQL Server 2012+中,值得测试新SEQUENCE功能的行为。具体而言,sp_sequence_get_range存储过程从序列对象生成一系列序列值。让我们把这个练习留给读者。

答案 2 :(得分:0)

您可以按如下方式在整个表格上设置独占锁定。

请注意X中的TABLOCKX - 这会使锁定独占。

BEGIN TRANSACTION;

-- select one dummy row, and set an exclusive lock 
-- on the whole table (TABLOCKX)

SELECT TOP 1 * 
FROM yourtab
WITH (TABLOCKX, HOLDLOCK);

-- Your INSERT statements goes here:
INSERT INTO yourtab(....) ... 

COMMIT TRANSACTION;

答案 3 :(得分:0)

哪个版本的SQL Sever?从SQL Server 2012开始,您可以创建所谓的序列。这些是为调用者创建唯一编号的对象。 序列保证是线程安全的。

在您的情况下,您需要一个按步骤5增加的序列:

CREATE SEQUENCE myseq 
AS int
START WITH 1
INCREMENT BY 5

在您的主题中,您可以使用NEXT VALUE检索下一个值:

DECLARE @var int ;
SET @var = NEXT VALUE FOR myseq ;

然后,您可以使用此值并在表中插入值。有了这个,你可以不用锁定你的桌子。