【问题标题】:B-trees, databases, sequential vs. random inserts, and speed. Random is winningB 树、数据库、顺序插入与随机插入以及速度。随机获胜
【发布时间】:2011-06-03 15:24:47
【问题描述】:

编辑

@Remus 更正了我的测试模式。您可以在下面的答案中看到更正后的版本。

我接受了将 INT 替换为 DECIMAL(29,0) 的建议,结果是:

十进制:2133
GUID:1836

随机插入仍然胜出,即使行稍大。

尽管解释表明随机插入比顺序插入慢,但这些基准测试表明它们显然更快。我得到的解释与基准不一致。因此,我的问题仍然集中在 b 树、顺序插入和速度上。

...

我从经验中知道,当数据按顺序(无论方向如何)添加到它们时,b 树的性能很差。但是,当数据随机添加时,会获得最佳性能。

这很容易用 RB-Tree 之类的东西来演示。顺序写入会导致执行最大数量的树平衡。

我知道很少有数据库使用二叉树,而是使用 n 阶平衡树。从逻辑上讲,我假设它们在顺序输入方面会遭受与二叉树相似的命运。

这激发了我的好奇心。

如果是这样,那么可以推断出写入顺序 ID(例如在 IDENTITY(1,1) 中)会导致树发生多次重新平衡。我看到 许多 帖子反对 GUID,因为“这些会导致随机写入”。我从不使用 GUID,但让我印象深刻的是,这个“坏”点实际上是一个点。

所以我决定测试一下。这是我的代码:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[T1](
    [ID] [int] NOT NULL
 CONSTRAINT [T1_1] PRIMARY KEY CLUSTERED ([ID] ASC) 
)
GO

CREATE TABLE [dbo].[T2](
    [ID] [uniqueidentifier] NOT NULL
 CONSTRAINT [T2_1] PRIMARY KEY CLUSTERED ([ID] ASC)
)

GO

declare @i int, @t1 datetime, @t2 datetime, @t3 datetime, @c char(300)

set @t1 = GETDATE()
set @i = 1

while @i < 2000 begin
    insert into T2 values (NEWID(), @c)
    set @i = @i + 1
end

set @t2 = GETDATE()
WAITFOR delay '0:0:10'
set @t3 = GETDATE()
set @i = 1

while @i < 2000 begin
    insert into T1 values (@i, @c)
    set @i = @i + 1
end

select DATEDIFF(ms, @t1, @t2) AS [Int], DATEDIFF(ms, @t3, getdate()) AS [GUID]

drop table T1
drop table T2

请注意,我不会因为行的相当大的额外大小而减少创建 GUID 的任何时间也不。我的机器上的结果如下:

内部:17,340 毫秒 GUID:6,746 毫秒

这意味着在此测试中,16 字节的随机插入4 字节的顺序插入几乎 3 倍。 p>

有人愿意对此发表评论吗?

附言。我知道这不是一个问题。这是一个讨论邀请,与学习最佳编程有关。

【问题讨论】:

  • 添加 char(3000) 列或 char(500) 列以减少每页的行数密度。第二次运行时会发生什么? DB必须增长吗?然后在 char 列上添加一个非聚集索引(如果
  • 我做到了 (char(3000))。结果:国际:7,406; GUID:22,286。字符(300):内部:6630,GUID:5,816。为什么?

标签: sql sql-server benchmarking clustered-index


【解决方案1】:

翻转操作,int更快..你考虑到日志和数据文件的增长了吗?分别运行每个

declare @i int, @t1 datetime, @t2 datetime

set @t1 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T2 values (NEWID())
    set @i = @i + 1
END


set @t2 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T1 values (@i)
    set @i = @i + 1
end



select DATEDIFF(ms, @t1, @t2) AS [UID], DATEDIFF(ms, @t2, getdate()) AS [Int]

UUID 的问题是在对它们进行集群而不使用 NEWSEQUENTIALID() 时,它们会导致分页符和表碎片

现在试试这个,你会发现几乎一样

declare @i int, @t1 datetime, @t2 datetime

set @t1 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T2 values (NEWID())
    set @i = @i + 1
END
select DATEDIFF(ms, @t1, getdate()) 

set @t1 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T1 values (@i)
    set @i = @i + 1
end



select DATEDIFF(ms, @t1, getdate())

反过来

declare @i int, @t1 datetime, @t2 datetime



set @t1 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T1 values (@i)
    set @i = @i + 1
end

set @t1 = GETDATE()
set @i = 1

while @i < 10000 begin
    insert into T2 values (NEWID())
    set @i = @i + 1
END
select DATEDIFF(ms, @t1, getdate())

【讨论】:

  • 我试过了,你是对的。不过,我永远不会使用 UID 作为密钥。我的问题实际上是关于 b-tree 重新排序。
  • “UUID 的问题是在对它们进行聚类而不使用 NEWSEQUENTIALID() 时会导致分页符和表碎片” - 这是因为插入的随机性吗?
  • 是的,这是正确的,因为它需要在页面上腾出空间,然后使用 NEWSEQUENTIALID() 进行拆分,这不会发生
  • 当然,如果有人有一个很大的 varchar 作为 PK,并且值有些随机,这可能会更糟。
【解决方案2】:

您没有测量 INSERT 速度。您正在测量您的日志刷新性能。由于您在每次 INSERT 后提交,所有这些测试都在等待提交以强化日志。这与 INSERT 性能几乎没有关系。当 SET NOCOUNT 为OFF时,请不要发布“性能”测量值...

因此,让我们尝试一下,避免不必要的服务器-客户端聊天,使用适当大小的数据、批量提交和预先增长的数据库:

:setvar dbname testdb
:setvar testsize 1000000
:setvar batchsize 1000

use master;
go

if db_id('$(dbname)') is not null
begin
    drop database [$(dbname)];
end
go

create database [$(dbname)] 
    on (name='test_data', filename='c:\temp\test_data.mdf', size=10gb)
    log on (name='test_log', filename='c:\temp\test_log.ldf', size=100mb);
go

use [$(dbname)];
go  

CREATE TABLE [dbo].[T1](
    [ID] [int] NOT NULL
 CONSTRAINT [T1_1] PRIMARY KEY CLUSTERED ([ID] ASC) 
)
GO

CREATE TABLE [dbo].[T2](
    [ID] [uniqueidentifier] NOT NULL
 CONSTRAINT [T2_1] PRIMARY KEY CLUSTERED ([ID] ASC)
)
GO

set nocount on;
go

declare @i int, @t1 datetime, @t2 datetime

set @t1 = GETDATE()
set @i = 1

begin transaction;
while @i < $(testsize) begin
    insert into T1 values (@i)
    set @i = @i + 1
    if @i % $(batchsize) = 0
    begin
        commit;
        begin transaction;
    end
end
commit

set @t2 = GETDATE()
set @i = 1
begin transaction
while @i < $(testsize) begin
    insert into T2 values (NEWID())
    set @i = @i + 1
    if @i % $(batchsize) = 0
    begin
        commit;
        begin transaction;
    end
end
commit

select DATEDIFF(ms, @t1, @t2) AS [Int], DATEDIFF(ms, @t2, getdate()) AS [UID]

drop table T1
drop table T2

INTS:18 秒
引导:23 秒

QED

【讨论】:

  • @Remus,我没有设置 SET NOCOUNT OFF。但是,为什么这会影响这个基准?
  • @IanC 默认 NOCOUNT 为 OFF,您必须明确将其设为 ON
  • SET NOCOUNT OFF(默认值)在每次插入行数后发送回客户端 (1 rows inserted)。所以服务器现在必须等待客户端消费这些消息。
  • Tools\Options\Query 执行检查By default, open new queries in SQLCMD mode
  • 还要确保启用了即时文件初始化,不想等待 10Gb 文件清零...见msdn.microsoft.com/en-us/library/ms175935.aspx
【解决方案3】:

我预计在真正的数据库中,索引的重新平衡是一个小问题,因为大量索引条目将适合单个块并且很长。

可能成为更多问题的可能是对包含所有新条目的单个块的争用。 Oracle 具有以相反顺序存储密钥字节的功能,以便将新条目分散到所有块中:http://oracletoday.blogspot.com/2006/09/there-is-option-to-create-index.html 不了解其他数据库。

【讨论】:

  • 我最近想测试一些东西。我创建了一个包含两个 int 列的表,第一个 PK IDENTITY(1,1),第二个有自己的索引。插入 1600 万条记录只用了 24 多小时……每秒不到 200 条。这种可怕的表现促使了这篇文章。
  • 也许 Oracle 这样做是为了避免我提到的问题。
  • 最后一页插入锁存器争用仅在高端系统(16-32 CPU 及更高)上可见。见sqlcat.com/technicalnotes/archive/2009/09/22/…
  • @IanC:每秒 200 次插入很可能是由于缺少批量提交。每秒大约 200-350 次日志刷新是普通 IO 子系统可以驱动的。您必须分批提交以实现 ETL 的任何性能。正常的 OLTP 负载受益于许多并发用户,并且日志刷新的成本由许多并发请求分摊,所以不是什么大问题。
  • @Remus 我从您的代码中看到了这一点,谢谢。我几乎从来没有运行过这样的插入,所以我从来不知道你所说的好处。但现在已经注意到了。
猜你喜欢
  • 2013-05-29
  • 1970-01-01
  • 2011-11-01
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
相关资源
最近更新 更多