我想知道如何(如果可能的话)以一种方式插入行,以确保一个线程的插入行的键将获得序列号。
例如,如果两个线程同时执行:
我一定不要得到:
显然,如果每个线程执行某种循环并执行5次" INSERT INTO ..."命令它不会起作用,因为另一个线程可以插入。
但即使一个线程只使用一个INSERT命令来插入很多行,是否足以保证密钥是顺序的?
如果是的话,你能帮我找一下它的记录吗?因为我没有。
如果不是,怎么可能确保?
编辑为什么我要关注连续数字:
它主要是一个性能问题,我们实际上有一个整数不是自动递增列,每个线程锁定该行,手动增加列值所需的任何数字,而不是释放行。
问题在于,一次只能插入一个线程,通过测试我们发现在新表自动增量列中插入行,让sql server管理身份分配,要快得多。 锁定整个表不是一个选项,因为它会导致与锁定公共行相同的问题。
我想确保插入的行id对于单个线程插入是顺序的原因是为了减少代码重构的需要,这实际上通过仅保留第一个数字和计数来工作,因此代码可以推断出什么是其他数字。
将数字作为连续数并不是一个商业问题,所以如果不可能,我们只需要将每个行号保留在一个数组中,但是有更多的代码可以用这种方式进行重构,所以我'我试图尽可能避免它。
请记住,我完全清楚设计可能并不理想,但我正在使用传统的“泥球”#34;我无法重新设计的系统。
答案 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
计算MAX
和ID
V
(1000个插入行的每个块)。如果所有IDENTITY
值都是按顺序生成的,则MAX
和MIN
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 ;
然后,您可以使用此值并在表中插入值。有了这个,你可以不用锁定你的桌子。