为什么SQL Server偶尔会决定不使用这个索引?

时间:2014-03-17 03:19:09

标签: sql-server performance optimization indexing

我遇到了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]

这曾经很完美。但现在偶尔也只在某些数据库中,查询需要永远,并给我超时。所以我运行一个“show execution plan”并意识到在某些情况下,取决于表中的数据,SQL Server决定不使用我的索引并改为使用PK索引。我验证这在其他数据库上运行相同的执行计划,工作正常并且正在使用索引。

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

感谢您的关注!

更新 我已经尝试更新STATICS并没有帮助。我现在暂时避免使用HINT,所以问题仍然存在:为什么SQL Server会选择一种更低效的方式来执行我的查询,如果它有索引呢?

更新II 经过多次测试后,我可以最终解决问题,尽管我并不完全理解为什么会这样。我将索引更改为:

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]

Basicaly我改变了索引中字段的顺序,并且查询开始使用索引作为预期。我仍然觉得SQL Server如何选择使用或不使用特定查询的索引。谢谢大家。

3 个答案:

答案 0 :(得分:1)

你必须找到很多关于Query优化器如何选择正确索引的文章。如果不在谷歌搜索的东西。 我可以指出一个开始。

Index Selection and the Query Optimizer

简单的答案如下:

"基于索引使用历史,统计信息,插入/更新/删除的行数等等。查询优化器发现使用PK索引比使用其他非群集索引的成本更低。 "

现在,您会对Query Optimizer如何找到它有很多疑问?这将需要一些家庭作业。

虽然在你的具体情况下,我不同意" Femi"如上所述,试图运行"更新统计数据"因为还有其他一些情况,Update Statistics也无济于事。 听起来您已经在此查询上测试了此索引,如果您确定只希望该查询在100%的时间内使用该索引,请使用查询提示并指定需要使用此索引。通过这种方式,您可以始终确保使用此索引。

注意:您必须对各种数据加载进行足够的测试,以确保在任何情况下都不会使用此索引或不接受。一旦您使用Query提示,每次执行都将仅使用该提示,并且Optimizer将始终使用该索引提出执行计划。

答案 1 :(得分:0)

在这种特定情况下很难说清楚,但查询计划程序通常会查看它对特定表的统计信息,并决定使用错误的索引(对于某些错误的定义;可能只是你认为它应该使用的索引)。尝试在表格上运行UPDATE STATISTICS,看看查询计划程序是否达到了一组不同的决策。

答案 2 :(得分:0)

确定优化器选择或不选择给定索引的原因可能有点暗淡。但是,我注意到,您可能正在使用更好的索引。具体做法是:

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),因此上述索引将远小于未过滤的版本。