sql-server - 覆盖索引如何满足多个查询?

标签 sql-server indexing covering-index

我继承了 Azure 中托管的 MS Sql 数据库。 为了提高性能,我阅读了很多有关索引和覆盖索引的内容。 (也许这是我找到的最完整的读物:https://www.red-gate.com/simple-talk/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/)

但仍有一个疑问......

例如,对于下面的计费表(大约有 800 万行),我发现查询的 where 子句中最常用的字段是(在联接内或不在联接内): PAYMENT_DATE、DUE_DATE、CUSTOMER_ID、DELAY_DAYS、AMOUNT .

CREATE TABLE [dbo].[BILLING](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [CHANGED_DATE] [datetime] NULL,
    [INCLUDED_DATE] [datetime] NULL,
    [CHANGED_USER_ID] [int] NULL,
    [INCLUDED_USER_ID] [int] NULL,
    [BILL_CODE] [varchar](255) NOT NULL,
    [PAYMENT_DATE] [datetime] NULL,
    [DUE_DATE] [datetime] NOT NULL,
    [AMOUNT] [float] NOT NULL,
    [AMOUNT_PAYED] [float] NULL,
    [CUSTOMER_ID] [int] NOT NULL,
    [OUR_NUMBER] [varchar](200) NULL,
    [TYPE] [varchar](250) NULL,
    [BANK_ID] [int] NULL,
    [ISSUE_DATE] [datetime] NULL,
    [STATE] [varchar](20) NULL,
    [DUNNING_STATE_ID] [int] NULL,
    [OPEN_VALUE] [float] NULL,
    [ACCREDIT_VALUE] [float] NULL,
    [LOWER_VALUE] [float] NULL,
    [DISCCOUNT_VALUE] [float] NULL,
    [INTEREST_VALUE] [float] NULL,
    [FINE_VALUE] [float] NULL,
    [RECEIVED_AMOUNT] [float] NULL,
    [DELAY_DAYS] [int] NULL,
    [BRANCH_ID] [int] NULL,
    [FIELD1] [varchar](250) NULL,
    [FIELD2] [varchar](250) NULL,
    [FIELD3] [varchar](250) NULL,
    [FIELD4] [varchar](250) NULL,
    [FIELD5] [varchar](250) NULL,
    [OBS1] [varchar](250) NULL,
    [OBS2] [varchar](250) NULL,
    [OBS3] [varchar](250) NULL,
    [INTEREST_RATE] [float] NULL,
    [INTEREST_CALC] [float] NULL,
    [AGREEMENT_STATE] [varchar](20) NULL,
    [AGREEMENT_ID] [int] NULL,
PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)

此外,优化的目标查询对 select 子句进行计算: AMOUNT,DELAY_DAYS,COUNT(ID)。 例如:

SELECT
        T.CUSTOMER_ID AS CUSTOMER_ID
        , COUNT(T.ID) AS NUM_BILLS
        , COUNT(
            CASE
                WHEN T.DELAY_DAYS <= 0 THEN 1
                ELSE NULL
            END
        ) AS DEPOSITS
        , COUNT(
            CASE
                WHEN T.DELAY_DAYS > 0 THEN 1
                ELSE NULL
            END
        ) AS DEFAULTED
        , COUNT(
            CASE
                WHEN T.DELAY_DAYS BETWEEN 30 AND 60 THEN 1
                ELSE NULL
            END
        ) AS DEFAULTED_30
        , COUNT(
            CASE
                WHEN T.DELAY_DAYS BETWEEN 60 AND 90 THEN 1
                ELSE NULL
            END
        ) AS DEFAULTED_60
        , COUNT(
            CASE
                WHEN T.DELAY_DAYS > 90 THEN 1
                ELSE NULL
            END
        ) AS DEFAULTED_90
        , MAX(T.DELAY_DAYS) AS MAX_DEFAULTED_TIME
        , SUM(
            CASE
                WHEN T.DELAY_DAYS > 0 THEN T.DELAY_DAYS
                ELSE 0
            END
        ) AS SUM_DEFAULTED_TIME
        , SUM(T.AMOUNT) AS AMOUNT
        , SUM(
            CASE
                WHEN T.DELAY_DAYS > 0 THEN T.AMOUNT
                ELSE 0
            END
        ) AS DEFAULTED_AMOUNT
    FROM BILLING T
    WHERE
        T.DUE_DATE < GETDATE()
        AND T.AMOUNT > 0
    GROUP BY
        T.CUSTOMER_ID

因此,对我来说,很明显以下索引可以解决我的所有问题:

CREATE NONCLUSTERED INDEX [ix_Titulo_main_fields] ON [dbo].[BILLING]
(
    [PAYMENT_DATE] ASC,
    [DUE_DATE] DESC,
    [AMOUNT] ASC,
    [CUSTOMER_ID] ASC,
    [STATE] ASC,
    [DELAY_DAYS] ASC,
    [BRANCH_ID] ASC,
    [AGREEMENT_ID] ASC
)
INCLUDE (   [BILLING_CODE],
    [AGREEMENT_STATE],
)
GO;

相比之下,当我在 Management Studio 上请求查询计划时,SQL Server 不使用此索引并建议我创建一个新索引:

CREATE NONCLUSTERED INDEX [ix_billing_due_date_amount] ON [dbo].[billing]
(
    [due_date] ASC,
    [amount] ASC
)
INCLUDE (   [customer_id],
    [delay_days])
GO

所以,疑问是:
覆盖索引是否需要正是 WHERE 子句搜索的内容?
如果这是真的,覆盖索引如何满足多个查询?
否则为什么前面的索引不能满足查询呢?

我真的不知道我在哪里错过了一些东西......

提前致谢!

最佳答案

顺序很重要。由于您建议的索引以 [ payment_date ] 开头,但查询谓词不包含 [ payment_date ],因此该索引不太可能比表扫描更有优势。

单个索引可以覆盖多个查询。索引的第一个列出的字段几乎总是需要位于所有查询的谓词中。为了改进结果,也可以将此逻辑应用于第二个字段、第三个字段等。

当一个职位有多个选择时,一种选择可能比另一种表现更好。

旁注:Oracle 有一个称为“索引跳过扫描”的功能,即使前导列不在谓词中,也允许使用索引。当前导列几乎没有不同的值(来自 learningintheopen.org )时,它是有效的。

关于sql-server - 覆盖索引如何满足多个查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51483767/

相关文章:

sql - 如何选择特定字符后的数据

sql - 在 SQL 更新期间跳过过去的转换错误并继续的最佳方法是什么?

sql-server - SQL Server 中的报表生成器和 VS 中的 Reporting Services 之间的区别

c - 后台文件索引器(Windows、*Nix、跨平台)

mysql - 如何检查 MySQL 查询是否正在使用覆盖索引?

sql - 验证 SQL Server 中表中的字符串 - CLR 函数或 T-SQL(已更新问题)

MySQL 索引 - 最佳实践是什么?

entity-framework - EF 代码优先 : CreateIndex - Covering Index

mysql - 具有基于枚举的过滤器的平面 MySQL 表出乎意料地慢

javascript - 谷歌可以抓取javascript生成的链接吗?