为什么没有索引这个查询更快?

时间:2010-11-29 18:59:15

标签: sql tsql sql-server-2008 query-performance

我继承了一个新系统,我正在尝试对数据进行一些改进。我正在努力改进这个表,似乎无法理解我的发现。

我有以下表结构:

CREATE TABLE [dbo].[Calls](
    [CallID] [varchar](8) NOT NULL PRIMARY KEY,
    [RecvdDate] [varchar](10) NOT NULL,
    [yr] [int] NOT NULL,
    [Mnth] [int] NOT NULL,
    [CallStatus] [varchar](50) NOT NULL,
    [Category] [varchar](100) NOT NULL,
    [QCall] [varchar](15) NOT NULL,
    [KOUNT] [int] NOT NULL)

此表中包含约220k条记录。我需要返回日期大于特定日期的所有记录。在这种情况下12/1/2009。此查询将返回大约66k记录,运行大约需要4秒。从我过去的系统来看,这似乎很高。特别是考虑到表中的记录很少。所以我想把时间缩短。

所以我想知道将这种方法降低的一些好方法是什么?我尝试在表中添加日期列并将字符串日期转换为实际日期列。然后我在该日期列添加了索引,但时间保持不变。鉴于没有那么多记录,我可以看到表扫描如何快速但我认为索引可能会缩短时间。

我还考虑过查询月份和年份列。但我还没有尝试过。如果可能的话,我希望将其保留在日期栏之外。但如果不是,我可以改变它。

感谢任何帮助。

编辑:这是我试图运行的查询并测试表的速度。我通常把列放出来,但为了简单起见,我使用了*:

SELECT *
FROM _FirstSlaLevel_Tickets_New
WHERE TicketRecvdDateTime >= '12/01/2009'

编辑2:所以我提到我曾尝试使用包含recvddate数据的日期列创建一个表,但是作为日期而不是varchar。这就是TicketRecvdDateTime列在上面的查询中。我对这个表运行的原始查询是:

SELECT *
FROM Calls
WHERE CAST(RecvdDate AS DATE) >= '12/01/2009'

5 个答案:

答案 0 :(得分:4)

您可能会遇到SQL Server中称为“临界点”的内容。即使列上有适当的索引,如果返回的预期行数超过某个阈值(“临界点”),SQL Server也可能决定进行表扫描。

在您的示例中,这可能是因为您正在转换数据库中行数的1/4。以下是一篇很好的文章,解释了这一点:http://www.sqlskills.com/BLOGS/KIMBERLY/category/The-Tipping-Point.aspx

答案 1 :(得分:4)

SELECT *通常表现不佳。

索引将被忽略,或者您最终会在聚簇索引中使用键/书签查找。无论如何:两者都可能运行得很糟糕。

例如,如果您有此查询以及TicketRecvdDateTime INCLUDEd CallStatus上的索引,那么它很可能会按预期运行。这将是covering

SELECT CallStatus
FROM _FirstSlaLevel_Tickets_New
WHERE TicketRecvdDateTime >= '12/01/2009'

这是Randy Minder的回答:对于少量行,键/书签查找可能足够便宜,但对于大量的表数据则不行。

答案 2 :(得分:3)

你的查询没有索引更快(或者更准确地说,与indeX相同的速度是相同的)因为RecvdDate上的索引总是是在像CAST(RecvdDate AS DATE) >= '12/01/2009'这样的表达式中被忽略。这是一个非SARG表达式,因为它需要通过函数转换列。为了将此事件视为,您必须在要编入索引的列上表达您的过滤条件完全,而不是基于它的表达式。这将是第一步。

还有更多步骤:

  • 删除日期的VARCHAR(10)列,并将其替换为相应的DATE或DATETIME列。将日期和/或时间存储为字符串存在问题。不仅用于索引,还用于正确性。
  • 经常在基于列的范围内扫描的表(大多数此类调用日志表)应该由该列进行聚类。
  • 您极不可能真正需要yrmnth列。如果你确实需要它们,那么你可能需要它们作为计算列。

CREATE TABLE [dbo].[Calls](
    [CallID] [varchar](8) NOT NULL,
    [RecvdDate] [datetime](10) NOT NULL,
    [CallStatus] [varchar](50) NOT NULL,
    [Category] [varchar](100) NOT NULL,
    [QCall] [varchar](15) NOT NULL,
    [KOUNT] [int] NOT NULL,
    CONSTRAINT [PK_Calls_CallId] PRIMARY KEY NONCLUSTERED ([CallID]));

CREATE CLUSTERED INDEX cdxCalls ON Calls(RecvDate);

SELECT *
FROM Calls
WHERE RecvDate >= '12/01/2009';

当然,表格和索引的正确结构应该是仔细分析的结果,考虑所有因素,包括更新性能,其他查询等。我建议您首先浏览所有Designing Indexes中包含的主题。

答案 3 :(得分:0)

你能改变你的疑问吗?如果需要很少的列,则可以更改SELECT子句以返回更少的列。然后,您可以创建包含所有引用列的覆盖索引,包括TicketRecvdDateTime

您可以在TicketRecvdDateTime上创建索引,但您可能无法避免@Randy Minder讨论的引爆点。但是,对较小索引(小于表扫描)的扫描将返回较少的页面。

答案 4 :(得分:0)

假设RecvdDate是您正在谈论的TicketRecvdDateTime:

如果字段类型为DATE,则SQL Server仅比较单引号中的日期。您的查询可能将它们作为VARCHAR进行比较。尝试添加一行'99 / 99/0001'并查看它是否显示在底部。

如果是这样,您的查询结果不正确。将类型更改为DATE。

请注意,VARCHAR索引不好,DATETIME会这样做。

检查查询计划以查看其是否使用索引。如果DB与可用RAM相比较小,则可以简单地进行表扫描并将所有内容保存在内存中。

编辑:在看到您的CAST / DATETIME编辑时,让我指出从VARCHAR解析日期是一项非常昂贵的操作。你这样做了220k次。这会扼杀性能。

此外,您不再检查索引字段。与涉及索引字段的表达式进行比较时不使用索引。