【问题标题】:SqlServer Rand() questionSql Server Random() 问题
【发布时间】:2009-11-01 15:47:42
【问题描述】:

我正在编写一个程序,每次调用都需要获取一个随机数。这个过程是从我们的 .net Web 服务调用的。

我尝试使用 rand() 来实现它。但是,当我在几毫秒内多次调用存储过程时,我会遇到很多冲突,因为正在生成相同的随机数。如果后续调用之间有大约 20 或 30 毫秒的间隔,它似乎工作正常。

似乎 rand() 被 SqlServer 重新植入每个存储过程调用。据我了解,这是一个问题,因为一个随机数生成器应该播种一次,并且如果一个人重新播种对 rand 的每个调用,则不会得到一个好的伪随机数序列。此外,在 1 或 2 毫秒内对同一个 sp 的调用似乎得到了相同的值。

这是存储过程中的语句本身。

DECLARE @randomNumber char(9)

SET @randomNumber = RIGHT('00000' + CAST(CAST(rand()*100000 AS INT) AS VARCHAR(5)),5)
+ RIGHT('00000' + CAST(CAST(rand()*10000 AS INT) AS VARCHAR(4)),4)

有人有解决此问题的建议吗?

我是否必须编写自己的随机数生成器,该生成器只播种一次并将其状态保存在跨调用的表中? SQL Server 如何播种 rand()?它是真正随机的,还是如果您在单独的连接上彼此在 1 或 2 毫秒内调用一个 sp,是否会使用相同的种子播种,从而导致冲突?

【问题讨论】:

  • 您是否尝试过编写CLR存储过程并使用.NET框架的随机数能力?

标签: sql-server random


【解决方案1】:

如果您使用的是 SQL Server 2008,则可以使用 CRYPT_GEN_RANDOM() 函数。即使您尝试在一次查询执行中计算数百万个随机数并且没有任何种子问题,这也会随机化每一行的数据:

SELECT CAST(RIGHT(CAST(CAST(CRYPT_GEN_RANDOM(1) AS INT) AS VARCHAR(100)), 1) AS INT)

这是 BOL 文章的链接:

http://msdn.microsoft.com/en-us/library/cc627408.aspx

【讨论】:

    【解决方案2】:

    在您的示例中,将 rand()*10000 替换为 ABS(CHECKSUM(NEWID())) % 9999

    但是,对于 char(9):

    SELECT RIGHT('000000000' + CAST(ABS(CHECKSUM(NEWID()) % 999999999) AS char(9), 9)
    

    随机播种兰德...

    RAND(CHECKSUM(NEWID()))
    

    编辑:

    注意,RAND 在 SQL Server 中的实现很糟糕。不要使用它。

    【讨论】:

    • +!有效,但我想知道是否有关于 newid() 或校验和(newid()) 的分布的任何保证。计算newid()时不是用的MAC地址吗?
    • newid = GUID,所以统计数据相同。 CHECKSUM = 有符号 32 位 = 40 亿。
    • 目前看来是最好的解决方案。虽然,我也在研究一个使用 RNGCryptoServiceProvider 的 clr 存储过程。
    • @Jeff:我在这里用 SQL 回答了 RNGCryptoServiceProvider stackoverflow.com/questions/1521835/…
    【解决方案3】:

    您可以使用仅包含标识符字段的表来创建唯一编号以用作种子:

    declare
      @randomNumber char(9),
      @seed1 int,
      @seed2 int
    
    insert into SeedTable () values ()
    set @seed1 = scope_identity()
    
    insert into SeedTable () values ()
    set @seed2 = scope_identity()
    
    set @randomNumber = right('00000' + 
        cast(cast(rand(@seed1) * 100000 as int) as varchar(5)), 5) +
        right('00000' + 
        cast(cast(rand(@seed2) * 10000 as int) as varchar(4)), 4)
    
    if (@seed2 > 10000) truncate table SeedTable
    

    【讨论】:

    • 您是否打算创建一个名为 SeedTable 的表?
    • @Andomar:是的,这就是我所说的只有一个标识符的表格。
    • @Guffy:截断表重置标识列?
    • @Andomar:是的,确实如此。如果您希望身份继续存在,您可以使用删除来减小大小。
    【解决方案4】:

    RAND() function 有一个可选的种子参数,您可以使用它。如果您将最后生成的随机值作为种子传递给下一次调用 rand(),则可以保证获得一个新的随机数。

    感谢 gbn 指出种子是一个整数,而 rand() 返回一个浮点数。有了这些知识,这是一个工作示例!首先创建一个表:

    create table RandomNumber (number float)
    insert into RandomNumber values (rand())
    

    然后抓取一个随机数并将新数存储在事务中:

    declare @new float
    begin transaction
    select @new = rand(-2147483648 + 4294967295 * number)
        from RandomNumber with (updlock, holdlock)
    update RandomNumber set number = @new
    commit transaction
    print 'Next bingo number is: ' + cast(cast(@new*100 as int) as varchar)
    

    SQL Server 整数在 -2147483648 和 2147483647 之间变化,随机数是 0.0 和 1.0 之间的浮点数。所以-2147483648 + 4294967295 * number 应该涵盖所有可用整数。

    事务确保一次只有一个连接读取和存储一个新号码。因此,即使在与 SQL Server 的不同连接上,这些数字也是随机的。 (顺便说一下,我投了gbn的回答,好像容易多了。)

    【讨论】:

    • 这是一个有趣的想法,但是我必须将这个状态保存在一个表中,并在 sp 中的每个调用中查找它。好像很贵另外,如果我以这种方式设置种子,这会产生一个很好的伪随机数字序列吗?
    • @Andomar:RAND 采用 int 种子,因此每次迭代都播种相同的种子。在这里查看我的旧答案:stackoverflow.com/questions/1038681/…
    猜你喜欢
    • 2023-03-15
    • 2011-04-05
    • 1970-01-01
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多