覆盖索引如何满足多个查询?
How can a covering index satisfy more than one query?
我继承了 Azure 中托管的 MS Sql 数据库。
为了提高性能,我已经阅读了很多有关索引和覆盖索引的内容。
(也许这是我找到的最完整的阅读材料:https://www.red-gate.com/simple-talk/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/)
但还有一个疑问...
因此,例如,对于下面的帐单 table(大约有 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 服务器不使用此索引并建议我创建一个新索引:
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],因此索引不太可能比 table 扫描更有利。
可以让一个索引覆盖多个查询。索引的第一个列出的字段几乎总是需要在所有查询的谓词中。为了改善结果,也将此逻辑应用于第 2 个字段、第 3 个字段等。
当一个职位有多个选择时,一个选择可能比另一个表现更好。
旁注:Oracle 有一个名为 "index skip scan" 的特性,即使前导列不在谓词中也允许使用索引。当前导列几乎没有不同的值时有效(来自 learningintheopen.org)。
对于任何特定的查询,您当然可以创建专门的索引 - 优化器可以告诉您这一点。并且特定的查询将得到超级提升,其他类似的查询将或多或少地变得更快。然而,根据经验,我不专门为查询使用索引,也不喜欢多列索引,也不使用 include。可能会有罕见的例外,但通常我不会。为什么?优化器会提示您在查询范围内需要什么索引 - 在将 8-10 个左右的索引添加到相同的 table 之后,优化器将不再识别要使用的索引,更不用说 insert/update 延迟(尽管正确的索引甚至可以节省锁定时间 insert/update 的时间)。
对于您的情况,我应该使用 8 个单一索引,每个列一个,除非列已经是主键或 PK 的一部分。如果列是唯一的,请检查是否可以创建唯一索引而不是简单索引。这有很大帮助。
总的来说,在 table 上有 4-8 个单列索引是所有 sql 的最佳解决方法,最终将针对 table 执行。只要按照您的描述通过使用情况调查选择了这些列,这就是有效的。
这是因为最重要的是第一次过滤。在 3 秒内从 800 万行中过滤掉 10000 行是成功的——现在如何将 10000 行过滤到最后 10 行并不重要。也许那里也有一个索引,也许没有,但是您希望 table 扫描在哪里,以 800 万或 10000 过滤?
根据我的经验,一组不错的单列索引可以帮助 99% 的查询快速响应,因为它们会得到一个索引列来搜索开始。
有时查询会选择错误的索引 - 某些通用过滤器仅过滤掉 5%,而忽略过滤掉 95% 的过滤器。这可能是导致执行计划不佳的统计数据或基数估计器。您可以通过索引的查询提示来解决这个问题,您一定要始终使用它或强制执行 2012 基数估计器。
我继承了 Azure 中托管的 MS Sql 数据库。 为了提高性能,我已经阅读了很多有关索引和覆盖索引的内容。 (也许这是我找到的最完整的阅读材料:https://www.red-gate.com/simple-talk/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/)
但还有一个疑问...
因此,例如,对于下面的帐单 table(大约有 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 服务器不使用此索引并建议我创建一个新索引:
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],因此索引不太可能比 table 扫描更有利。
可以让一个索引覆盖多个查询。索引的第一个列出的字段几乎总是需要在所有查询的谓词中。为了改善结果,也将此逻辑应用于第 2 个字段、第 3 个字段等。
当一个职位有多个选择时,一个选择可能比另一个表现更好。
旁注:Oracle 有一个名为 "index skip scan" 的特性,即使前导列不在谓词中也允许使用索引。当前导列几乎没有不同的值时有效(来自 learningintheopen.org)。
对于任何特定的查询,您当然可以创建专门的索引 - 优化器可以告诉您这一点。并且特定的查询将得到超级提升,其他类似的查询将或多或少地变得更快。然而,根据经验,我不专门为查询使用索引,也不喜欢多列索引,也不使用 include。可能会有罕见的例外,但通常我不会。为什么?优化器会提示您在查询范围内需要什么索引 - 在将 8-10 个左右的索引添加到相同的 table 之后,优化器将不再识别要使用的索引,更不用说 insert/update 延迟(尽管正确的索引甚至可以节省锁定时间 insert/update 的时间)。
对于您的情况,我应该使用 8 个单一索引,每个列一个,除非列已经是主键或 PK 的一部分。如果列是唯一的,请检查是否可以创建唯一索引而不是简单索引。这有很大帮助。
总的来说,在 table 上有 4-8 个单列索引是所有 sql 的最佳解决方法,最终将针对 table 执行。只要按照您的描述通过使用情况调查选择了这些列,这就是有效的。
这是因为最重要的是第一次过滤。在 3 秒内从 800 万行中过滤掉 10000 行是成功的——现在如何将 10000 行过滤到最后 10 行并不重要。也许那里也有一个索引,也许没有,但是您希望 table 扫描在哪里,以 800 万或 10000 过滤?
根据我的经验,一组不错的单列索引可以帮助 99% 的查询快速响应,因为它们会得到一个索引列来搜索开始。
有时查询会选择错误的索引 - 某些通用过滤器仅过滤掉 5%,而忽略过滤掉 95% 的过滤器。这可能是导致执行计划不佳的统计数据或基数估计器。您可以通过索引的查询提示来解决这个问题,您一定要始终使用它或强制执行 2012 基数估计器。