【问题标题】:Big Table Advice (SQL Server)大表建​​议 (SQL Server)
【发布时间】:2009-11-20 15:41:50
【问题描述】:

我在访问我的一张表时遇到了极大的缓慢,我需要一些重构建议。抱歉,如果这不是此类事情的正确区域。

我正在开展一个项目,旨在报告我们内部服务器的服务器性能统计信息。我每晚都在处理 Windows 性能日志(12 个服务器,10 个性能计数器,每 15 秒记录一次)。我将数据存储在一个表中,如下所示:

CREATE TABLE [dbo].[log](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [logfile_id] [int] NOT NULL,
    [test_id] [int] NOT NULL,
    [timestamp] [datetime] NOT NULL,
    [value] [float] NOT NULL,
CONSTRAINT [PK_log] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH FILLFACTOR = 90 ON [PRIMARY]
) ON [PRIMARY]

目前有 16,529,131 行,并将继续增长。

我访问数据以生成报告并从冷融合创建图表,如下所示:

SET NOCOUNT ON

CREATE TABLE ##RowNumber ( RowNumber int IDENTITY (1, 1), log_id char(9) )

INSERT ##RowNumber (log_id)
SELECT l.id
FROM log l, logfile lf
WHERE lf.server_id = #arguments.server_id#
and l.test_id = #arguments.test_id#"
and l.timestamp >= #arguments.report_from#
and l.timestamp < #arguments.report_to#
and l.logfile_id = lf.id
order by l.timestamp asc

select rn.RowNumber, l.value, l.timestamp
from log l, logfile lf, ##RowNumber rn
where lf.server_id = #arguments.server_id#
and l.test_id = #arguments.test_id#
and l.logfile_id = lf.id
and rn.log_id = l.id
and ((rn.rownumber % #modu# = 0) or (rn.rownumber = 1)) 
order by l.timestamp asc

DROP TABLE ##RowNumber

SET NOCOUNT OFF

(对于非 CF 开发人员 #value# 插入 value## 映射到 #

我基本上创建了一个临时表,以便我可以使用行号来选择每 x 行。这样,我只选择可以显示的行数。这有帮助,但仍然很慢。

SQL Server Management Studio 告诉我我的索引如下(我几乎不知道如何正确使用索引):

IX_logfile_id (Non-Unique, Non-Clustered)
IX_test_id (Non-Unique, Non-Clustered)
IX_timestamp (Non-Unique, Non-Clustered)
PK_log (Clustered)

我将非常感谢任何可以提供一些建议以帮助我加快速度的人。我不介意重新组织事物,并且我可以完全控制项目(尽管可能不是通过服务器硬件)。

干杯(对不起,很长的帖子)

【问题讨论】:

  • 顺便说一句,1600 万行虽然很大,但绝非不可能。我们这里有一个 SQL Server 数据库,其中有一个包含大约 10 亿行的表,我们仍然可以在不到一秒的时间内执行查询。正如您所推断的,索引在这里非常重要。
  • Daniel 是对的,我们的数据仓库是建立在 sql server 平台上的,我们定期报告(而且速度也很快)超过 3.5 亿行的表。
  • 我很高兴我不负责您的数据库,我不知道从哪里开始!有趣的是,我在大学学习数据库设计作为我的 CS 课程的一部分,但他们甚至没有涉及索引或任何类型的真实数据维护,真的有点令人失望。

标签: sql-server coldfusion sql-server-2000


【解决方案1】:

您的问题是您选择了错误的集群密钥。没有人对通过 ID 检索一个特定的日志值感兴趣。如果你的系统和我见过的任何其他系统一样,那么所有查询都会要求:

  • 所有服务器在一个日期范围内的所有计数器
  • 特定日期范围内所有服务器的特定计数器值
  • 一个服务器在一个日期范围内的所有计数器
  • 特定服务器在一定日期范围内的特定计数器

鉴于表的大小,所有非聚集索引都是无用的。他们都会打index tipping point,保证,所以他们也可能不存在。我假设您的所有非聚集索引都定义为名称中字段的简单索引,没有包含字段。

我会假装我真的知道你的要求。您必须忘记有关存储的常识,并在每个非聚集索引中实际复制所有数据。这是我的建议:

  • 删除 [id] 上的聚簇索引,是没有用的。
  • 使用聚集索引(logfile_it、test_id、时间戳)组织表。
  • (test_id、logfile_id、时间戳)上的非集群索引包含(值)
  • (logfile_id,时间戳)包含(值)上的 NC 索引
  • (test_id,时间戳)上的 NC 索引包括(值)
  • (时间戳)包含(值)上的 NC 索引
  • 添加维护任务以定期重组所有索引,因为它们容易产生碎片

聚集索引涵盖查询“特定机器上特定计数器值的历史”。非聚集索引涵盖了各种其他可能的查询(随着时间的推移机器上的所有计数器,随着时间的推移所有机器上的特定计数器等)。

您注意到我没有对您的查询脚本发表任何评论。那是因为世界上没有任何事情可以使查询在您拥有的表结构上运行得更快。

现在你不应该做的一件事是实际执行我的建议。我说我要假装我知道你的要求。但我其实没有。我只是举了一个可能结构的例子。您真正应该做的是研究该主题并根据您的要求找出正确的索引结构:

还有一个关于“覆盖索引”的谷歌会带来很多好文章。

当然,归根结底,存储并不是免费的,因此您必须平衡对每种可能组合的非聚集索引的要求与保持数据库大小受控的要求。幸运的是,您有一个非常小而窄的表,因此将它复制到许多非聚集索引上没什么大不了的。此外,我不会担心插入性能,每个 15 秒的 120 个计数器意味着每秒插入 8-9 次,这没什么。

【讨论】:

  • 优秀的答案!非常感谢您的建议和链接。我会进一步研究这个主题,但你给了我一个很好的起点。
  • 对不起最后一件事。我只是想消化您的建议,当您说Non-clusterd index on (test_id, logfile_id, timestamp) include (value) 时,您的意思是拥有test_id, logfile_id, timestamp, value 的NC 索引还是include 列的特殊机制。抱歉,我有点困惑,因为它不在其他列的列表中,再次感谢。
  • 从 SQL 2005 开始,有一种特殊的机制来包含列。在 2000 年,您必须将“包含”列添加到索引键本身。
【解决方案2】:

我想到了几件事。

  1. 您需要保留这么多数据吗?如果没有,如果您想保留它,请考虑创建一个存档表(但不要创建它只是为了在每次运行查询时将其与主表连接)。

  2. 我会避免使用包含大量数据的临时表。请参阅这篇关于临时表性能以及如何避免使用它们的文章。

http://www.sql-server-performance.com/articles/per/derived_temp_tables_p1.aspx

  1. 您似乎缺少 server_id 字段上的索引。我会考虑使用此字段和其他字段创建覆盖索引。这里也有一篇文章。

http://www.sql-server-performance.com/tips/covering_indexes_p1.aspx

编辑

  1. 在这么短的时间内表中有这么多行,我还会检查索引是否存在碎片,这可能是导致运行缓慢的原因。在 SQL Server 2000 中,您可以使用DBCC SHOWCONTIG 命令。

查看此链接以获取信息http://technet.microsoft.com/en-us/library/cc966523.aspx

另外,请注意我已将这些项目编号为 1、2、3、4,但编辑器会自动重置它们

【讨论】:

  • 很棒的链接! 1. 到目前为止,我有 4 个月的数据,理想情况下,我希望在当前表中保留 6 个月,然后我可以考虑存档。 2. 我将使用方法 1(再次)运行一些测试。 server_id 是另一个表(称为日志文件)的一部分。每个服务器有多个日志文件,每个日志文件有多个日志。我还可以将它包含在索引中吗?
  • 不,如果该字段位于不同的表中,则不能将其包含在覆盖索引中。我已经用另一点更新了我的答案。
  • 运行这些检查后,我发现逻辑扫描碎片为 2.3%,扩展扫描碎片为 0.11%。我的页面密度为 90%,根据该文件,这是一个健康的数量。这些数字对我来说看起来不错,但我会确保按照该文档的建议添加碎片整理。
【解决方案3】:

有一次,当我还在使用 sql server 2000 时,我需要做一些分页,我遇到了一种让我大吃一惊的分页方法。看看这个方法。

DECLARE @Table TABLE(
        TimeVal DATETIME
)

DECLARE @StartVal INT
DECLARE @EndVal INT

SELECT  @StartVal = 51, @EndVal = 100

SELECT  *
FROM    (
            SELECT  TOP (@EndVal - @StartVal + 1)
                    *
            FROM    (
                        --select up to end number
                        SELECT  TOP (@EndVal)
                                *
                        FROM    @Table
                        ORDER BY TimeVal ASC
                    ) PageReversed
            ORDER BY TimeVal DESC
        ) PageVals
ORDER BY TimeVal ASC

举个例子

SELECT  *
FROM    (
            SELECT  TOP (@EndVal - @StartVal + 1)
                    *
            FROM    (
                        SELECT TOP (@EndVal)
                                l.id,
                                l.timestamp
                        FROM log l, logfile lf
                        WHERE lf.server_id = #arguments.server_id#
                        and l.test_id = #arguments.test_id#"
                        and l.timestamp >= #arguments.report_from#
                        and l.timestamp < #arguments.report_to#
                        and l.logfile_id = lf.id
                        order by l.timestamp asc
                    ) PageReversed ORDER BY timestamp DESC
        ) PageVals
ORDER BY timestamp ASC

【讨论】:

  • 这看起来是一个很好的模拟 MySQL 限制功能的方法,我非常想念它!问题是我不知道我想要的数据在哪里,我必须在日期之间找到数据,而且数据的顺序并不总是正确,所以我认为我现在不能使用它,但我肯定会保存以备后用。
  • 效果出奇的快!我看看能不能用这个非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多