在SQL中处理大量数据

时间:2016-12-11 19:00:50

标签: c# sql sql-server

我刚刚在工作中接管了一个项目,我的老板让我让它跑得更快。大。

因此,我已经确定了从我们的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'

正如您所看到的,它是一个非常基本的设置。主要问题是我们投入的大量数据 - 每个跟踪器大约每十秒一行,而且我们目前有超过一百个跟踪器。我们目前坐在大约1000万到1500万行之间。所以这给我留下了两个问题。

  • 如果我每秒插入10行(没有批量处理),我是不是在颠倒数据库?
  • 鉴于这是历史数据,所以一旦插入它就永远不会改变,有什么办法可以加快读取访问速度吗?

3 个答案:

答案 0 :(得分:3)

  1. 表上有太多非聚集索引 - 这会增加数据库的大小。
  2. 如果您在MachineryIdDateRecorded上有索引,那么您在[{1}}上确实不需要单独的索引。

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

    Clustered VS Non-Clustered

    不包含在非聚集索引

    当SQL Server执行您的SQL时,它首先在Non-Clustered Index中搜索所需的数据,然后它将返回到原始表(MachineryIdLink并获取其余的正如你所做的那些列bookmark lookup,但非聚集索引并没有所有列(这就是我认为正在发生的事情 - 在没有查询计划的情况下真的可以告诉)

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

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

    2. 我认为您应该在select *MachineryId而不是非群集索引上使用聚簇索引。一个表只能有一个Clustered Index(这是订单数据存储在硬盘上) - 因为您的大多数查询都是DateRecordredDateRecordred订单 - 最好存储它们那样,

    3. 此外,如果您确实在MachineryId中搜索任何查询,请尝试将其添加到同一群集索引

      重要提示:在进入LIVE之前在TEST环境中删除非聚集索引

      创建聚簇索引而不是非聚集索引,运行不同的查询 - 通过比较TrackerId和ç计Query Plans)来检查性能

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

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

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

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

答案 1 :(得分:2)

调整是针对每个查询,但无论如何 -
我看到你没有分区和索引,这意味着,无论你做什么。它总是导致全表扫描。

针对您的具体查询 -

create index MachineryReading_ix_MachineryReading_DateRecorded 
    on (MachineryReading,DateRecorded)

答案 2 :(得分: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的进展。但整体改进应该是如此之大,以至于所有这些都将是一场巨大的胜利。