【问题标题】:Is there a way to generate a ~million mac addresses more efficiently?有没有办法更有效地生成一百万个 mac 地址?
【发布时间】:2012-12-28 22:56:13
【问题描述】:

有没有办法更有效地生成约百万个 MAC 地址?

这是我正在做的事情,但是生成一百万个 MAC 地址并将它们插入到我的表中需要大约 10 分钟:

DECLARE @StartRange BINARY(6) 
DECLARE @EndRange BINARY(6) 
SET @StartRange = 0x0036D1F00000 
SET @EndRange = 0x0036D1FFFFFF 
--select convert(bigint,+ @EndRange) - convert(bigint,+ @StartRange) = 1048575
WHILE(convert(bigint, (SELECT IDENT_CURRENT('Mac_Address'))) < (convert(bigint, @EndRange) - convert(bigint, @StartRange)))
BEGIN
insert into Mac_Address (MacAddress)
select convert(BINARY(6),(convert(bigint, (SELECT IDENT_CURRENT('Mac_Address'))) + convert(bigint, @StartRange)))
END

这是我在网上找到的执行此操作的代码,它会在几秒钟内执行。但是它正在将 MAC 地址生成到系统表中:

DECLARE @StartRange BINARY(8) 
DECLARE @EndRange BINARY(8) 
SET @StartRange = 0x00000004A500114B 
SET @EndRange = 0x00000004A50F11FF 

--select convert(integer,+ @EndRange) - convert(integer,+ @StartRange) = 983220

select convert(BINARY(8),RW + convert(integer, @StartRange)) 
from
(select row_number() over(order by a.id) As RW from syscolumns,syscolumns a,syscolumns b ,syscolumns c) b --I do not understand this line very well.
where RW between 1 and (convert(integer, @EndRange) - convert(integer, @StartRange))

【问题讨论】:

  • 什么版本的 SQL Server?
  • SQL Server 2012 速成版。

标签: sql sql-server sql-server-2012-express


【解决方案1】:

这不是您所说的“将 MAC 地址生成到系统表中”。它只是使用syscolumns 表作为获取大量行的一种手段。您可以知道这一点,因为所有系统表都在FROM 子句中。要插入到表中,至少需要INSERTSELECT ... INTO

之所以这么快,是因为 SQL Server 针对“基于行”的操作进行了优化。在引擎内部,当然它必须遍历每一行,但它已经过大量优化。当您将 T-SQL 视为一种过程语言并一次循环遍历每个项目时,它无法使用这些优化,并且必须在完全不同的执行上下文中一次执行每个语句。

syscolumns 表本身毫无意义。它具有价值,因为它保证有很多行。它可以是任何有很多行的表——FROM 子句中的表只是被交叉连接在一起以获得它们的笛卡尔积(意味着很多行),因此Row_Number() 函数可以使用创建一个数字从 1 到 1,000,000 的行集。

如果您愿意,您可以使用这样的方法,它根本不会碰到任何“真实”表,它的执行速度应该一样快或更快。它所做的只是获取初始的SELECT 1 UNION ALL SELECT 1,并通过重复的CROSS JOINing 将其转换为43 亿行(但使用FROM Table1, Table2 的旧式语法,与FROM Table1 CROSS JOIN Table2 相同,但更短)。

我在 SQL 2008 中测试了以下内容,它运行良好。我的 SQL Server 需要 3 秒(此时 SQL Fiddle 错误地抛出错误,我已向管理员发送电子邮件)。

DECLARE
   @StartRange bigint = 0x0004A500114B,
   @EndRange bigint = 0x0004A50F11FF;

WITH -- make sure you terminate your prior statement by putting ; after it
L0 AS (SELECT 1 N UNION ALL SELECT 1),
L1 AS (SELECT 1 N FROM L0, L0 B),
L2 AS (SELECT 1 N FROM L1, L1 B),
L3 AS (SELECT 1 N FROM L2, L2 B),
L4 AS (SELECT 1 N FROM L3, L3 B),
L5 AS (SELECT 1 N FROM L4, L4 B),
Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) N FROM L5)
INSERT Mac_Address (MacAddress)
SELECT Convert(binary(6), N + @StartRange - 1)
FROM Nums
WHERE N <= @EndRange - @StartRange + 1

但是,我不能 100% 确定这将在 SQL Server 2012 的 Express 版本中运行。使用 syscolumns 版本实际上没有任何问题,您只需添加一个 INSERT 语句并更正一些问题(包括int 而不是bigintbinary(8) 而不是binary(6))。请注意,您可以通过在每个syscolumns 表之后添加WITH (NOLOCK)(或在查询之前使用SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 并在查询之后使用SET TRANSACTION ISOLATION LEVEL READ COMMITTED)来提高性能。这个查询也执行得很好(在我的服务器上 2 秒)。

DECLARE
   @StartRange bigint = 0x0004A500114B,
   @EndRange bigint = 0x0004A50F11FF;

INSERT Mac_Address (MacAddress) -- Add this line
SELECT Convert(binary(6), N + @StartRange - 1) 
FROM
(
    SELECT N = Row_Number() OVER (ORDER BY (SELECT 1))
    FROM
       syscolumns WITH (NOLOCK),
       syscolumns a WITH (NOLOCK),
       syscolumns b WITH (NOLOCK),
       syscolumns c WITH (NOLOCK)
) b
WHERE N <= @EndRange - @StartRange + 1;

【讨论】:

  • 对不起,我的误解。我想生成这些记录并将它们放入我的“Mac_Address”表中(就像我的第一个代码 sn-p)。据我了解,它执行得很快,因为他正在使用 syscolumns 表?反正有没有加快我插入代码sn-p的速度?谢谢。
  • 惊人的详细解释ErikE!我必须承认,上面的很多代码都超出了我的想象,但我至少从你的解释中理解了前提。谢谢你:)
  • @ErikE,我的心理解析器在您的代码上失败了...第一个示例不会生成 100 万行,对吗?而int/integer只会捕获低32位,导致数据截断错误(在SqlFiuddle中试过)。 ;)
  • @Lucero 我使用了 bigint,如果你看一下我的第一个代码 sn-p。另外,使用第一个代码 sn -p 的范围,它会生成 1048575 条记录。它在 23 秒内为我完成了这项工作。
  • @Mausimo 在运行代码之前删除您的表!发布的 CTE 只会生成 2^2^2^2^2 = 65536 个项目。
【解决方案2】:

第二个 sn-p 根本没有插入任何东西。它仅使用 syscolumns 表的(与自身交叉连接)行号来生成整数。

您可以为此使用任何其他表(前提是您有足够的行数或加入足够的次数来生成它)

【讨论】:

  • 这就是它执行如此之快的原因吗?我认为它正在生成到一个 sys 表中。我不太了解 from 行。
  • 是的,部分。第二个 sn-p 执行的操作更少。
  • 有什么办法可以改进我的第一个sn-p吗?我实际上需要将 Mac 地址插入到表中,例如我的第一个代码 sn-p。谢谢
【解决方案3】:

它并没有真正使用 syscolumns。它只是将它连接在一起以创建一个包含足够行数的表以供您的范围使用。如果您对此感到不舒服,可以执行以下操作:

with digits as (select 0 as dig union all
                select 1 union all
                select 2 union all
                select 3 union all
                select 4 union all
                select 5 union all
                select 6 union all
                select 7 union all
                select 8 union all
                select 9
               ),
      nums as (select dig1 + 10*dig2+100*dig3+1000*dig4+10000*dig5+100000*dig6 as num
               from dig dig1 cross join dig dig2 cross join
                    dig dig3 cross join dig dig4 cross join
                    dig dig5 cross join dig dig6
              )
select convert(BINARY(8), num + convert(integer, @StartRange)) 
from nums
where num + 1 between 1 and (convert(integer, @EndRange) - convert(integer, @StartRange))

【讨论】:

    【解决方案4】:

    以下内容很简单,在线SqlFiddle上运行大约一分钟:

    DECLARE @StartRange bigint;
    DECLARE @EndRange bigint;
    SET @StartRange = 0x0036D1F00000;
    SET @EndRange =   0x0036D1FFFFFF; 
    
    WITH cteMacAddress AS (
      SELECT @StartRange MacAddress
      UNION ALL
      SELECT MacAddress+1 FROM cteMacAddress
      WHERE MacAddress <= @EndRange
    )
    INSERT Mac_Address
      SELECT CAST(MacAddress AS BINARY(6))
      FROM cteMacAddress
      OPTION (MAXRECURSION 0);
    
    SELECT COUNT(*) GeneratedAddressCount
      FROM Mac_Address;
    

    【讨论】:

    • 你的小提琴花了我 296.5 秒,而不是 60 秒。
    • @ErikE,我想这很大程度上取决于服务器的负载。在您自己的计算机上运行它,看看它的效果如何 - 仅使用 CTE 生成数字序列被认为是次优的,但它仍然比像 OP 那样在循环中这样做要快一个数量级。
    • 好的,在我自己的服务器上花了 24 秒,而我的答案中的查询需要 3 秒和 2 秒。感谢您的所有更正-我今天确实有很多错误。 :)
    • 呃,我可怜的服务器 :( 记住其他人也在写反对它的查询 - 请温柔
    猜你喜欢
    • 2014-12-26
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 2019-10-15
    • 2021-12-08
    相关资源
    最近更新 更多