【问题标题】:Why is this query faster without index?为什么这个查询在没有索引的情况下更快?
【发布时间】:2011-05-17 11:05:42
【问题描述】:

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

我的表结构如下:

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'

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

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

【问题讨论】:

  • 请问您正在运行的确切查询是什么?
  • 是的,很抱歉。让我编辑和添加。我忘了包括。
  • 什么是 TicketRecvdDateTime?那是某处的计算列吗?或者为什么不在您的表格声明中?
  • 抱歉,查询来自我尝试使用日期列而不是日期字段的 varchar 创建的新表。让我修复原始用途的查询。

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


【解决方案1】:

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

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

【讨论】:

  • 这很可能是因为我注意到有索引或没有索引我在查询中获得相同的速度。我在想,也许只是在表中记录数的某个点上,表扫描可以像索引扫描一样快地执行。但想看看其他人是否有更好的想法。
  • 您应该检查其他答案。虽然这可能是真的,但人们忽略了索引是在 varchar 上的这一点,而不是实际日期......看看下面的 Remus Rusanu,并练习翻转实际执行计划以了解她/他的观点。跨度>
【解决方案2】:

SELECT * 通常表现不佳。

要么索引将被忽略,要么您最终会通过键/书签查找聚集索引。没关系:两者都可能运行不佳。

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

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

这是对 Randy Minder 回答的补充:键/书签查找对于少数行可能足够便宜,但对于大部分表数据来说却不是。

【讨论】:

  • 我同意 * 不是一个好的选择。为了简单起见,我只在帖子中包含了它。但是我运行的查询列出了查询中的所有列。由于此查询的 * 或所有列之间没有区别,我只发布了更简单的。
【解决方案3】:

您的查询在没有索引的情况下更快(或者更准确地说,在没有索引的情况下速度相同),因为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中包含的主题。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

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

      请注意,VARCHAR 不能很好地索引,DATETIME 可以。

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-14
        • 2010-12-06
        • 1970-01-01
        • 2013-06-17
        • 2021-03-09
        • 2014-09-13
        • 2023-03-29
        • 1970-01-01
        相关资源
        最近更新 更多