【问题标题】:Handling large amounts of data in SQL在 SQL 中处理大量数据
【发布时间】:2017-04-26 16:09:09
【问题描述】:

我刚刚接手了一个工作项目,我的老板要求我让它运行得更快。太好了。

因此,我已经确定了从我们的 SQL 服务器搜索一个特定表的主要瓶颈之一,对于带有一些过滤器的选择查询,这可能需要长达一分钟,有时甚至更长时间在它上面运行。以下是 C# Entity Framework 生成的 SQL(减去所有 GO 语句):

CREATE TABLE [dbo].[MachineryReading](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Location] [geometry] NULL,
    [Latitude] [float] NOT NULL,
    [Longitude] [float] NOT NULL,
    [Altitude] [float] NULL,
    [Odometer] [int] NULL,
    [Speed] [float] NULL,
    [BatteryLevel] [int] NULL,
    [PinFlags] [bigint] NOT NULL, -- Deprecated field, this is now stored in a separate table
    [DateRecorded] [datetime] NOT NULL,
    [DateReceived] [datetime] NOT NULL,
    [Satellites] [int] NOT NULL,
    [HDOP] [float] NOT NULL,
    [MachineryId] [int] NOT NULL,
    [TrackerId] [int] NOT NULL,
    [ReportType] [nvarchar](1) NULL,
    [FixStatus] [int] NOT NULL,
    [AlarmStatus] [int] NOT NULL,
    [OperationalSeconds] [int] NOT NULL,
 CONSTRAINT [PK_dbo.MachineryReading] PRIMARY KEY NONCLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

ALTER TABLE [dbo].[MachineryReading] ADD  DEFAULT ((0)) FOR [FixStatus]
ALTER TABLE [dbo].[MachineryReading] ADD  DEFAULT ((0)) FOR [AlarmStatus]
ALTER TABLE [dbo].[MachineryReading] ADD  DEFAULT ((0)) FOR [OperationalSeconds]
ALTER TABLE [dbo].[MachineryReading] WITH CHECK ADD  CONSTRAINT [FK_dbo.MachineryReading_dbo.Machinery_MachineryId] FOREIGN KEY([MachineryId])
REFERENCES [dbo].[Machinery] ([Id])
  ON DELETE CASCADE
ALTER TABLE [dbo].[MachineryReading] CHECK CONSTRAINT [FK_dbo.MachineryReading_dbo.Machinery_MachineryId]
ALTER TABLE [dbo].[MachineryReading] WITH CHECK ADD  CONSTRAINT [FK_dbo.MachineryReading_dbo.Tracker_TrackerId] FOREIGN KEY([TrackerId])
  REFERENCES [dbo].[Tracker] ([Id])
  ON DELETE CASCADE
ALTER TABLE [dbo].[MachineryReading] CHECK CONSTRAINT [FK_dbo.MachineryReading_dbo.Tracker_TrackerId]

该表在MachineryIdTrackerIdDateRecorded 上有索引:

CREATE NONCLUSTERED INDEX [IX_MachineryId] ON [dbo].[MachineryReading]
(
    [MachineryId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

CREATE NONCLUSTERED INDEX [IX_MachineryId_DateRecorded] ON [dbo].[MachineryReading]
(
    [MachineryId] ASC,
    [DateRecorded] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

CREATE NONCLUSTERED INDEX [IX_TrackerId] ON [dbo].[MachineryReading]
(
    [TrackerId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

当我们从该表中选择时,我们几乎总是对给定日期范围内的一台机器或跟踪器感兴趣:

SELECT *
FROM MachineryReading
WHERE MachineryId = 2127 AND
      DateRecorded > '2016-12-08 00:00:10.009' AND DateRecorded < '2016-12-11 18:32:41.734'

如您所见,这是一个非常基本的设置。主要问题是我们放入其中的数据量很大——每个跟踪器每十秒大约一行,目前我们有一百多个跟踪器。我们目前坐在大约 10-15 百万行。所以这给我留下了两个问题。

  • 如果我每秒插入 10 行(不进行批处理),我是否会破坏数据库?
  • 鉴于这是历史数据,所以一旦插入它就永远不会改变,我可以做些什么来加快读取访问速度?

【问题讨论】:

  • select 语句在哪里 - 你添加了 create table sql
  • 哦,对了,我应该也加一下。
  • 你有延迟加载活动吗?
  • 提高数据访问性能的一种方法是创建索引——索引应该基于你一直在运行的 sql 语句——在你的情况下,我认为你应该在MachineryId 和 DateRecorded - 根据要求按升序或降序排列-
  • 1 - 请发布创建索引的sql - 2. 你检查过索引的碎片吗?

标签: c# sql sql-server


【解决方案1】:

调整是按查询进行的,但无论如何 -
我看到您没有分区也没有索引,这意味着,无论您做什么。它总是会导致全表扫描。

针对您的具体查询 -

create index MachineryReading_ix_MachineryReading_DateRecorded 
    on (MachineryReading,DateRecorded)

【讨论】:

    【解决方案2】:

    首先,在几乎任何合理的情况下,每秒 10 次插入都是非常可行的。

    其次,您需要一个索引。对于这个查询:

    SELECT *
    FROM MachineryReading
    WHERE MachineryId = 2127 AND
          DateRecorded > '2016-12-08 00:00:10.009' AND DateRecorded < '2016-12-11 18:32:41.734';
    

    您需要MachineryReading(MachineryId, DateRecorded) 的索引。这可能会解决您的性能问题。

    如果您对跟踪器有类似的查询,那么您希望在MachineryReading(TrackerId, DateRecorded) 上建立索引。

    这些会稍微阻碍inserts 的进展。但是整体的改进应该是很大的,这一切都将是一个巨大的胜利。

    【讨论】:

      【解决方案3】:
      1. 表上的非聚集索引过多 - 这会增加数据库的大小。

      如果您在 MachineryIdDateRecorded 上有一个索引 - 您真的不需要在 MachineryId 上单独一个索引。

      使用 3 个非聚集索引 - 还有 3 个数据副本

      Clustered VS Non-Clustered

      非聚集索引中不包含

      当 SQL Server 执行您的 SQL 时,它首先在非聚集索引中搜索所需数据,然后返回原始表 (bookmark lookup) Link 并获取其余列正在做select *,但非聚集索引没有所有列(这就是我认为正在发生的事情 - 没有查询计划真的无法判断)

      在非聚集索引中包含列:https://stackoverflow.com/a/1308325/1910735

      1. 您应该维护索引 - 通过创建维护计划来检查碎片并每周重建或重组索引。

      2. 我真的认为您应该在 MachineryIdDateRecordred 上使用聚集索引,而不是非聚集索引。一张表只能有一个聚集索引(这是存储在硬盘上的订单数据)-因为您的大多数查询将按DateRecordredMachineryId 顺序进行-最好以这种方式存储它们,

      另外,如果您确实在任何查询中通过 TrackerId 进行搜索,请尝试将其添加到同一个聚集索引中

      重要提示:在上线前删除测试环境中的非聚集索引

      创建聚集索引而不是非聚集索引,运行不同的查询 - 通过比较 Query Plans 和 STATISTICS IO 检查性能

      索引和 SQL 查询帮助的一些资源:

      在此处订阅时事通讯并下载第一响应者工具包: https://www.brentozar.com/?s=first+responder

      它现在是开源的 - 但我不知道它是否有实际的 PDF 入门和帮助文件(无论如何都可以在上面的链接中订阅 - 每周的文章/教程)

      https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit

      【讨论】:

      • 您关于书签查找的观点是最有价值的——我们在非聚集索引中有查询字段,但是一旦我们选择了其他列,它就必须进行查找,因此更喜欢不同的、次优的索引。在索引中包含我们想要的值已经解决了这个问题。
      • @AndrewWilliamson 我还建议您通过删除非聚集索引并创建聚集索引来进行检查 - 因为非聚集索引是数据的副本 - 因为它每个都是跟踪平台机器每天可能有 5K 条记录 - 如果您有 1000 辆汽车将数据保存到同一个表中(每天 5K * 1K 条记录),最终表大小将增加到 8 GB,但使用非聚集索引同一个表可能是 14 或 16 GB。我认为有一种方法可以检查索引大小。
      • 感谢您的建议。问的时候我不知道,但是表实际上是在 DateRecorded 字段上分区的,并且用作我们的聚集索引。我和进行分区的人都不知道它是如何工作的,所以我们不知道要在那个上进行什么更改。减小尺寸目前不是优先事项,所以我有一些时间来学习。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-06-11
      • 2021-08-06
      • 1970-01-01
      • 2017-02-09
      • 1970-01-01
      • 2020-07-15
      相关资源
      最近更新 更多