【问题标题】:Why SQL Server occasionally decides not to use this index?为什么 SQL Server 偶尔会决定不使用此索引?
【发布时间】:2014-03-17 03:19:08
【问题描述】:

我有一个复杂的 SQL Server 问题。

我管理 40 个结构相同但数据不同的数据库。这些数据库大小从 2 MB 到 10 GB 的数据不等。这些数据库的主表是:

CREATE TABLE [dbo].[Eventos](
    [ID_Evento] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
    [FechaGPS] [datetime] NOT NULL,
    [FechaRecepcion] [datetime] NOT NULL,
    [CodigoUnico] [varchar](30) COLLATE Modern_Spanish_CI_AS NULL,
    [ID_Movil] [int] NULL,
    [CodigoEvento] [char](5) COLLATE Modern_Spanish_CI_AS NULL,
    [EventoData] [varchar](150) COLLATE Modern_Spanish_CI_AS NULL,
    [EventoAlarma] [bit] NOT NULL CONSTRAINT [DF_Table_1_Alarma]  DEFAULT ((0)),
    [Ack] [bit] NOT NULL CONSTRAINT [DF_Eventos_Ack]  DEFAULT ((0)),
    [Procesado] [bit] NOT NULL CONSTRAINT [DF_Eventos_Procesado]  DEFAULT ((0)),
    [Latitud] [float] NULL,
    [Longitud] [float] NULL,
    [Velocidad] [float] NULL,
    [Rumbo] [smallint] NULL,
    [Satelites] [tinyint] NULL,
    [EventoCerca] [bit] NOT NULL CONSTRAINT [DF_Eventos_FueraCerca]  DEFAULT ((0)),
    [ID_CercaElectronica] [int] NULL,
    [Direccion] [varchar](250) COLLATE Modern_Spanish_CI_AS NULL,
    [Localidad] [varchar](150) COLLATE Modern_Spanish_CI_AS NULL,
    [Provincia] [varchar](100) COLLATE Modern_Spanish_CI_AS NULL,
    [Pais] [varchar](50) COLLATE Modern_Spanish_CI_AS NULL,
    [EstadoEntradas] [char](16) COLLATE Modern_Spanish_CI_AS NULL,
    [DentroFuera] [char](1) COLLATE Modern_Spanish_CI_AS NULL,
    [Enviado] [bit] NOT NULL CONSTRAINT [DF_Eventos_Enviado]  DEFAULT ((0)),
    [SeñalGSM] [int] NOT NULL DEFAULT ((0)),
    [GeoCode] [bit] NOT NULL CONSTRAINT [DF_Eventos_GeoCode]  DEFAULT ((0)),
    [Contacto] [bit] NOT NULL CONSTRAINT [DF_Eventos_Contacto]  DEFAULT ((0)),
 CONSTRAINT [PK_Eventos] PRIMARY KEY CLUSTERED 
(
    [ID_Evento] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
USE [ABS]
GO
ALTER TABLE [dbo].[Eventos]  WITH CHECK ADD  CONSTRAINT [FK_Eventos_Eventos] FOREIGN KEY([ID_Evento])
REFERENCES [dbo].[Eventos] ([ID_Evento])

我还有一个每 n 秒运行一次的循环来处理这些记录(只有新记录并将它们标记为已处理)。此过程使用此查询:

SELECT     
   Tbl.ID_Cliente, Ev.ID_Evento, Tbl.ID_Movil, Ev.EventoData, Tbl.Evento, 
   Tbl.ID_CercaElectronica, Ev.Latitud, Ev.Longitud, Tbl.EsAlarma, Ev.FechaGPS, 
   Tbl.AlarmaVelocidad, Ev.Velocidad, Ev.CodigoEvento
FROM         
   dbo.Eventos AS Ev 
INNER JOIN
   (SELECT 
        Det.CodigoEvento, Mov.CodigoUnico, Mov.ID_Cliente, Mov.ID_Movil, Det.Evento, 
        Mov.ID_CercaElectronica, Det.EsAlarma, Mov.AlarmaVelocidad 
    FROM 
        dbo.Moviles Mov 
    INNER JOIN 
        dbo.GruposEventos AS GE 
    INNER JOIN  
        dbo.GruposEventosDet AS Det ON Det.ID_GrupoEventos = GE.ID_GrupoEventos
     ON GE.ID_GrupoEventos = Mov.ID_GrupoEventos) as Tbl ON EV.CodigoUnico = Tbl.CodigoUnico AND Ev.CodigoEvento = Tbl.CodigoEvento
WHERE     
    (Ev.Procesado = 0)

在某些数据库中,该表可以有超过 1.000.000 条记录。因此,为了优化流程,我使用 SQL 助手为该查询创建了特定的索引以进行优化:

CREATE NONCLUSTERED INDEX [OptimizadorProcesarEventos] ON [dbo].[Eventos] 
(
    [Procesado] ASC,
    [CodigoEvento] ASC,
    [CodigoUnico] ASC,
    [FechaGPS] ASC
)
INCLUDE ( [ID_Evento],
[EventoData],
[Latitud],
[Longitud],
[Velocidad]) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]

这曾经完美无缺。但是现在偶尔并且仅在某些数据库中,查询需要永远并且给我超时。所以我运行了一个“显示执行计划”,并意识到在某些情况下,根据表中的数据,SQL Server 决定不使用我的索引,而是使用 PK 索引。我验证了这在其他运行正常的数据库上运行相同的执行计划并且正在使用索引。

所以我的问题是:为什么 SQL Server 在某些情况下决定不使用我的索引?

感谢您的关注!

更新 我已经尝试更新统计信息并没有帮助。我现在更倾向于避免使用 HINT,所以问题仍然存在:如果 SQL Server 有索引,为什么要选择更低效的方式来执行我的查询?

更新二 经过多次测试,我终于可以解决问题,即使我不太明白为什么会这样。我将索引更改为:

CREATE NONCLUSTERED INDEX [OptimizadorProcesarEventos] ON [dbo].[Eventos] 
    (
        [CodigoUnico] ASC,                          
        [CodigoEvento] ASC,
        [Procesado] ASC,
        [FechaGPS] ASC
    )
    INCLUDE ( [ID_Evento],
    [EventoData],
    [Latitud],
    [Longitud],
    [Velocidad]) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]

基本上我改变了索引中字段的顺序,查询立即开始使用索引。 SQL Server 如何选择在特定查询中使用或不使用索引对我来说仍然是个谜。谢谢大家。

【问题讨论】:

    标签: sql-server performance optimization indexing


    【解决方案1】:

    您一定已经找到很多关于查询优化器如何选择正确索引的文章。如果不是在谷歌上搜索一些东西。 我可以指出一个开始。

    Index Selection and the Query Optimizer

    简单的答案如下:

    “根据索引使用历史、统计数据、插入/更新/删除的行数等......查询优化器发现使用 PK 索引比使用其他非聚集索引成本更低。”

    现在您会有很多关于查询优化器是如何发现这一点的问题的?这将需要一些家庭作业。

    尽管在您的具体情况下,我不同意“Femi”尝试运行“更新统计”,因为还有其他一些情况,更新统计也无济于事。 听起来您已经在此查询上测试了此索引,如果您确定只希望该查询 100% 使用此索引,请使用查询提示并指定需要使用此索引。通过这种方式,您始终可以确保将使用此索引。

    注意:您必须对各种数据负载进行足够多的测试,以确保在任何情况下都不会使用此索引或不可接受。一旦你使用了查询提示,每次执行都将只使用它,优化器将始终使用该索引提出执行计划。

    【讨论】:

    • 感谢您的回答,但问题还是一样。我不担心查询优化器如何构建我的索引,因为它显然对我的查询很有用,我的问题是为什么 SQL 服务器在某些情况下决定不使用它并选择以更低效的方式进行查询!我已经阅读了有关指定索引的提示,我知道这是我应该尽量避免的一种做法。再次感谢!
    【解决方案2】:

    在这种特定情况下很难说,但查询规划器通常会查看它对特定表的统计信息并决定使用 wrong 索引(对于一些错误的定义;可能只是不是您认为它应该使用的索引)。尝试在表上运行 UPDATE STATISTICS 并查看查询计划器是否得出不同的决策集。

    【讨论】:

      【解决方案3】:

      确定优化器选择或不选择给定索引的原因可能有点像一门黑暗的艺术。但是,我确实注意到,您可能正在使用更好的索引。具体来说:

      CREATE NONCLUSTERED INDEX [OptimizadorProcesarEventos] ON [dbo].[Eventos] 
      (
          [Procesado] ASC,
          [CodigoEvento] ASC,
          [CodigoUnico] ASC,
          [FechaGPS] ASC
      )
      INCLUDE ( [ID_Evento],
        [EventoData],
        [Latitud],
        [Longitud],
        [Velocidad]) 
      WHERE Procesado = 0 -- this makes it a filtered index
      WITH (SORT_IN_TEMPDB = OFF, 
        DROP_EXISTING = OFF, 
        IGNORE_DUP_KEY = OFF, 
        ONLINE = OFF)
      ON [PRIMARY]
      

      这是基于我的假设,即在任何给定时间,表中的大多数行都已处理(即 Procesado = 1),因此上述索引将比未过滤的版本小得多。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-25
        • 2012-02-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多