有效地生成唯一随机数

时间:2015-08-18 18:31:31

标签: sql-server tsql sql-server-2012

我们正在使用概述here的技术生成无冲突的随机记录ID。简而言之,我们创建一个包含每个可能ID的随机排序表,并在使用时将每个记录标记为“Taken”。

我使用以下存储过程来获取ID:

$statsTable = "someTable";
$userTable = "someOtherTable";

$someData = "SELECT stats.* FROM $statsTable stats, $userTable user 
WHERE user.some_status = '0' 
AND (stats.some_value BETWEEN $rangeFrom AND $rangeTo) 
ORDER BY stats.some_value ASC 
LIMIT 0,10";

then mysqli_query and so on...

ID的检索必须是原子操作,因为可以同时插入。

对于大型插入(1000万以上),这个过程非常慢,因为我必须通过游标来传递表格。

IdMasterList有一个架构:

ALTER PROCEDURE spc_GetId @retVal BIGINT OUTPUT
AS
  DECLARE @curUpdate TABLE (Id BIGINT);

  SET NOCOUNT ON;
  UPDATE IdMasterList SET Taken=1 
    OUTPUT DELETED.Id INTO @curUpdate
    WHERE ID=(SELECT TOP 1 ID FROM IdMasterList WITH (INDEX(IX_Taken)) WHERE Taken IS NULL ORDER BY SeqNo);

  SELECT TOP 1 @retVal=Id FROM @curUpdate;
  RETURN;

IX_Taken索引是:

SeqNo (BIGINT, NOT NULL) (PK) -- sequence of ordered numbers
Id (BIGINT)                   -- sequence of random numbers
Taken (BIT, NULL)             -- 1 if taken, NULL if not

我通常以这种方式用Ids填充表格:

CREATE NONCLUSTERED INDEX (IX_Taken) ON IdMasterList (Taken ASC)

问题:

  1. 我有什么方法可以改进SP以更快地提取ID?
  2. 条件索引是否会提高性能(我还没有测试过,因为 IdMasterList 非常大)?
  3. 有没有更好的方法用这些ID填充表格?

3 个答案:

答案 0 :(得分:1)

在每张桌子上放置BigInt的PK inden

insert into user (name)  
values ()..... 

update user set = user.ID = id.ID  
  from id
  left join usr 
    on usr.PK = id.PK 
 where user.ID = null;

一个

insert into user (name) value ("justsaynotocursor");
set @PK = select select SCOPE_IDENTITY();
update user set ID = (select ID from id where PK = @PK);

答案 1 :(得分:1)

与SQL Server中的大多数内容一样,如果您使用游标,那么您做错了。

由于您使用的是SQL Server 2012,因此可以使用SEQUENCE跟踪已使用的随机值,并有效地替换Taken列。

CREATE SEQUENCE SeqNoSequence
    AS bigint
    START WITH 1    -- Start with the first SeqNo that is not taken yet
    CACHE 1000;     -- Increase the cache size if you regularly need large blocks

用法:

CREATE TABLE #tmp
(
    recNo       bigint,
    SeqNo       bigint
)

INSERT INTO #tmp (recNo, SeqNo)
    SELECT      recNo,
                NEXT VALUE FOR SeqNoSequence
    FROM        Adds

UPDATE Adds
    SET         id = m.id
    FROM        Adds            a
    INNER JOIN  #tmp            tmp     ON a.recNo = tmp.recNo
    INNER JOIN  IdMasterList    m       ON tmp.SeqNo = m.SeqNo

SEQUENCE是原子的。对NEXT VALUE FOR SeqNoSequence的后续调用保证返回唯一值,即使对于并行进程也是如此。请注意SeqNo可能存在差距,但对于速度的大幅提升,这是一个非常小的折衷。

答案 2 :(得分:1)

我想到了很少的想法:

  • 尝试删除顶部,内部选择等有助于提高ID提取的性能(查看统计信息和查询计划):

    UPDATE top(1) IdMasterList 
    SET @retVal = Id, Taken=1
    WHERE Taken IS NULL
    
  • 将索引更改为过滤索引,因为我假设您不需要获取已获取的数字。如果我没记错的话,你不能为NULL值做这个,所以你需要把Taken改为0/1。

  • 你的问题究竟是什么?获取单个ID或10多万个ID?是否由光标和光盘引起的CPU / I / O等问题? ID获取逻辑,还是并行进程被其他进程阻止?

  • 使用序列对象获取SeqNo。然后使用它返回的值从idMasterList中获取Id。如果您在IdMasterList序列中没有空白,这可能会有效。

  • 使用READPAST提示可以帮助阻止,对于CPU / I / O问题,您应该尝试优化SQL。

  • 如果原因纯粹是表格是热点,并且没有其他简单的解决方案似乎有帮助,请将其拆分为多个表并使用某种简单的逻辑(甚至@@ spid,rand()或类似的东西)决定从哪个表中获取ID。您需要更多检查所有表格是否都有免费号码,但它不应该那么糟糕。

  • 创建不同的程序(甚至表格)来处理单个ID,数百个ID和数百万个ID的提取。