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

时间:2018-07-23 16:51:14

标签: 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子句要搜索的内容?
如果属实,覆盖索引如何满足多个查询?
否则,为什么以前的索引不能满足查询条件?

我真的不知道我在哪里错过任何东西...

谢谢!

2 个答案:

答案 0 :(得分:3)

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

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

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

侧面说明:Oracle具有称为“索引跳过扫描”的功能,即使前导列不在谓词中,该功能也允许使用索引。当前导列具有很少的不同值(来自learningintheopen.org时)有效。

答案 1 :(得分:1)

对于任何特定查询,您当然都可以创建一个专门的索引-优化器可以告诉您这一点。并且特定查询将被超级增强,而其他类似查询则或多或少地变得更快。但是,根据经验,我不使用专门用于查询的索引,也不喜欢多列索引,也不使用包含。可能会有罕见的例外,但通常我不会。为什么?优化器会提示您有关查询范围内需要哪些索引的信息-在将8-10个索引添加到同一表后,优化器将不再识别要使用的索引,更不用说插入/更新延迟了(尽管正确的索引即使在锁定时间进行插入/更新也可以节省时间。

对于您的情况,我应该使用8个单索引,每列一个,除非一个列已经是主键或PK的一部分。如果列是唯一的,请检查是否可以创建唯一索引而不是简单索引。这很有帮助。

总体而言,在一个表上具有4-8个单列索引是最终将对该表执行的所有sql的最佳解决方法。只要您按照描述在使用情况调查中选中了这些列,这就是有效的。

这是因为最重要的是第一次过滤。在3秒钟内成功滤除800万中的10000行是成功的-现在如何将10000行过滤到10个最终并不真正重要。也许那里也没有索引,但您想在哪里扫描800万或10000的表?

根据我的经验,一组不错的单列索引可帮助99%的查询快速响应,因为它们会获得索引列来搜索开始。

有时查询选择了错误的索引-某些仅过滤5%的通用过滤器,而忽略了过滤95%的过滤器。这可能是不良的统计信息或基数估计量,从而导致不良的执行计划。您可以通过查询提示来克服此问题,因为您一定可以随时使用索引或强制执行2012年基数估算器。