代码之家  ›  专栏  ›  技术社区  ›  spinon

为什么这个查询在没有索引的情况下更快?

  •  5
  • spinon  · 技术社区  · 14 年前

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

    我有以下表格结构:

    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条记录。我需要返回日期大于特定日期的所有记录。在这种情况下,2009年12月1日。此查询将返回大约66K条记录,运行大约需要4秒钟。从过去的系统来看,我在这方面做的工作似乎很高。尤其是考虑到表中的记录很少。所以我想缩短时间。

    所以我想知道什么样的好方法可以把它降下来?我尝试向表中添加日期列,并将字符串日期转换为实际日期列。然后我在那个日期栏上添加了一个索引,但时间保持不变。鉴于没有那么多的记录,我可以看到表扫描的速度有多快,但我认为索引可以缩短扫描时间。

    我还考虑过只查询月份和年份列。但我还没试过。如果可能的话,我想把它放在日期栏之外。但如果没有,我可以改变它。

    感谢您的帮助。

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

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

    edit 2:所以我提到我试图创建一个包含recvddate数据的日期列的表,但是作为日期而不是varchar。这就是上面查询中TicketRecvdDateTime列的内容。我针对该表运行的原始查询是:

    SELECT *
    FROM Calls
    WHERE CAST(RecvdDate AS DATE) >= '12/01/2009'
    
    5 回复  |  直到 14 年前
        1
  •  4
  •   Randy Minder    14 年前

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

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

        2
  •  4
  •   gbn    14 年前

    SELECT * 通常表现不佳。

    索引将被忽略,或者您将在聚集索引中进行键/书签查找。不管怎样:两个都可能跑得不好。

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

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

    这是兰迪·明德回答的另外一个问题:键/书签查找对于少数行来说可能足够便宜,但对于表数据的大部分来说却不够便宜。

        3
  •  3
  •   Remus Rusanu    14 年前

    如果没有索引(或者更准确地说,与索引的速度相同),则查询速度更快,因为 RecvdDate 总是 在这样的表达式中被忽略 CAST(RecvdDate AS DATE) >= '12/01/2009' . 这是一个不可sarg的表达式,因为它要求通过函数转换列。为了这个索引 需要考虑的事件 ,您必须表达筛选条件 确切地 在被索引的列上,而不是在基于它的表达式上。这将是第一步。

    还有更多步骤:

    • 去掉日期的varchar(10)列,并将其替换为适当的日期或日期时间列。将日期和/或时间存储为字符串会遇到许多问题。不仅用于索引,还用于正确性。
    • 经常在基于列的范围内扫描的表(大多数调用日志表都是这样)应该由该列聚集。
    • 你不太可能真的需要 yr mnth 柱。如果您确实需要它们,那么您可能需要它们作为计算列。

    .

    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 .

        4
  •  0
  •   bobs    14 年前

    你能改变你的查询吗?如果需要很少的列,可以更改select子句以返回较少的列。然后,您可以创建一个覆盖索引,其中包括所有引用的列,包括 TicketRecvdDateTime .

    您可以在上创建索引 票务日期时间 但你可能无法避免@randy minder讨论的转折点。但是,对较小索引(小于表扫描)的扫描将返回较少的页面。

        5
  •  0
  •   Bubba    14 年前

    假设recvddate是您所说的ticketrecvddatetime:

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

    如果是这样,则查询结果不正确。将类型更改为“日期”。

    注意varchar索引不好,datetime索引不好。

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

    编辑:在查看您的cast/datetime编辑时,让我指出从varchar解析日期是一个非常昂贵的操作。你做了22万次。这将扼杀性能。

    此外,您不再检查索引字段。包含索引字段的表达式的比较不使用索引。