SQL 日期范围查询索引
SQL index for date range query
几天来,我一直在努力提高我的数据库的性能,关于 SQL 服务器数据库中的索引,我仍然对一些问题感到困惑。
我会尽量提供信息。
我的数据库目前包含大约 10 万行,并且会继续增长,因此我正在努力寻找一种方法使其运行得更快。
我也在写信给这个table,所以如果你的建议会大大减少写作时间,请告诉我。
总体目标是 select 日期范围内具有特定名称的所有行。
这通常是 select 超过 3,000 行,哈哈 ...
Table 架构:
CREATE TABLE [dbo].[reports]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[IsDuplicate] [bit] NOT NULL,
[IsNotValid] [bit] NOT NULL,
[Time] [datetime] NOT NULL,
[ShortDate] [date] NOT NULL,
[Source] [nvarchar](350) NULL,
[Email] [nvarchar](350) NULL,
CONSTRAINT [PK_dbo.reports]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
这是我正在使用的 SQL 查询:
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate BETWEEN '2017-10-13' AND '2017-10-15'
据我所知,在不影响写入时间的情况下提高效率的最佳方法是在 Source
和 ShortDate
.
上创建非聚集索引
我喜欢这样,索引模式:
CREATE NONCLUSTERED INDEX [Source&Time]
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)
现在我们进入了让我完全迷失的棘手部分,上面的索引有时有效,有时一半有效,有时根本无效....
(不确定这是否重要,但目前 90% 的数据库行具有相同的源,尽管这种情况不会持续很长时间)
在下面的查询中,根本没有使用索引,我使用的是 SQL Server 2014,在执行计划中它说它只使用聚集索引扫描:
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
在这个查询中,索引根本没有被使用,尽管我从 SQL 服务器得到一个建议,创建一个日期在前,源在后的索引。 .我读到索引应该按查询的顺序制作?它还说要包含我 selecting 中的所有列,这是必须的吗?...我再次读到我应该只在索引中包含我正在搜索的列。
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate = '2017-10-13'
SQL 服务器索引建议 -
/* The Query Processor estimates that implementing the following
index could improve the query cost by 86.2728%. */
/*
USE [db]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[reports] ([ShortDate], [Source])
INCLUDE ([id], [IsDuplicate], [IsNotValid], [Time], [Email])
GO
*/
现在我尝试使用索引 SQL 服务器建议我创建并且它有效,似乎它使用上面两个查询使用 100% 的非聚集索引。
我尝试使用此索引,但删除了包含的列,但它不起作用...似乎我必须在索引中包含我正在 selecting 的所有列?
顺便说一句,如果我包含所有列,它也可以在使用我创建的索引时使用。
总而言之:索引的顺序似乎并不重要,因为它在创建 Source + ShortDate
和 ShortDate + Source
时都有效
但出于某种原因,必须包含所有列...(这将极大地影响对此 table 的写作?)
非常感谢您的阅读,我的目标是了解为什么会发生这种情况以及否则我应该做什么(不仅仅是解决方案,因为我还需要将其应用于其他项目)。
干杯:)
索引的包含列适用于您选择的列。
由于您执行 select *
(这不是好的做法),因此不会使用索引,因为它必须查找整个 table 才能获取列的值。
对于您的方案,我将删除默认的聚簇索引(如果有的话)并使用以下语句创建一个新的聚簇索引:
USE [db]
GO
CREATE CLUSTERED INDEX CIX_reports
ON [dbo].[reports] ([ShortDate],[Source])
GO
一般来说,您希望索引从选择性最高(即过滤掉最可能的记录)到选择性最低;如果列的基数较低,查询优化器可能会忽略它。
这很直观 - 如果您有一本 phone 的书,并且您正在寻找名为 "smith" 的人,首字母为 "A",您希望从搜索开始首先是"smith",然后是"A",而不是所有首字母为"A"的人,然后过滤掉那些叫"Smith"的人。毕竟,概率是每 26 个人中就有一个人拥有最初的 "A".
因此,在您的示例中,我猜您在短日期内具有广泛的值 - 因此这是查询优化器试图过滤掉的第一列。你说你在 "source" 中有几个不同的值,所以查询优化器可能决定忽略它;在那种情况下,该索引中的第二列也没有用。
索引中 where 子句的顺序无关紧要 - 您可以交换它们并获得完全相同的结果,因此查询优化器会忽略它们。
编辑:
所以,是的,制作索引。假设您有一堆卡片需要排序 - 在您的第一个 运行 中,您想要移除尽可能多的卡片。假设它全部均匀分布——如果你有 1000 个单独的 short_dates 超过一百万行,这意味着如果你的第一个 运行 从 short_date 开始,你最终会得到 1000 个项目;如果按来源排序,则有 100000 行。
SQL 服务器中的索引部分是长期经验(以及许多小时的挫折)的专有技术,部分是黑魔法。不要因为太多而自责 - 这就是像 SO 这样的地方的理想选择 - 大量的大脑,大量的优化经验,你可以利用。
I read that the index should be made by the order the query is?
如果你读到这个 - 它绝对 NOT TRUE - 列的顺序 是 相关的 - 但以不同的方式:仅当您在查询的索引定义中指定 n 最左边的列 时,才会考虑复合索引(由多列组成)。
经典示例:一本 phone 的书,其索引为(城市、姓氏、名字)。这样的索引可能会用到:
- 在其
WHERE
子句中指定所有三列的查询中
- 在使用
city
和 lastname
的查询中(查找 "Detroit" 中的所有 "Miller")
- 或在仅按城市过滤的查询中
但如果您只想搜索 firstname
..... 那是,则可以永远使用它您需要了解的有关复合索引的技巧。但是,如果您总是使用索引中的所有列,那么它们的顺序通常并不真正相关 - 查询优化器会为您处理。
至于 包含的列 - 那些 仅 存储在非聚集索引的叶级别 - 它们是 NOT 是索引搜索结构的一部分,您不能为 WHERE
子句中包含的那些列指定过滤器值。
这些包含的列的主要好处是:如果您在非聚集索引中搜索,最后您实际上找到了您要查找的值 - 此时您有什么可用的?非聚集索引将存储非聚集索引定义中的列(ShortDate
和 Source
),并且它将存储 聚集键 (如果您有一个- 你 应该!) - 但别无其他。
所以在这种情况下,一旦找到匹配项,并且您的查询需要 table 的所有内容,SQL 服务器必须执行以下操作称为 键查找 (通常也称为 书签查找 ),其中它获取聚集键然后执行 Seek 对聚簇索引的操作,以获取包含您要查找的所有值的实际数据页。
如果您的索引中包含列,则非聚集索引的叶级页面包含
- 非聚集索引中定义的列
- 集群键列
- 您的
INCLUDE
语句中定义的所有其他列
如果这些列 "cover" 您的查询,例如提供您的查询需要的所有值,然后 SQL 服务器一旦找到您在非聚集索引中搜索的值就完成了 - 它可以从非聚集索引的叶级页面中获取它需要的所有值,并且它不需要对聚类索引进行另一次(昂贵的)键查找以获取实际值。
因此,尝试总是明确指定只有那些你真正需要的列在你的SELECT
中可能是有益的- 在这种情况下,您 可能能够 创建一个有效的 覆盖索引 为您的 SELECT
提供所有值 - 始终使用SELECT *
使这变得非常困难或几乎不可能.....
几天来,我一直在努力提高我的数据库的性能,关于 SQL 服务器数据库中的索引,我仍然对一些问题感到困惑。
我会尽量提供信息。
我的数据库目前包含大约 10 万行,并且会继续增长,因此我正在努力寻找一种方法使其运行得更快。
我也在写信给这个table,所以如果你的建议会大大减少写作时间,请告诉我。
总体目标是 select 日期范围内具有特定名称的所有行。
这通常是 select 超过 3,000 行,哈哈 ...
Table 架构:
CREATE TABLE [dbo].[reports]
(
[id] [int] IDENTITY(1,1) NOT NULL,
[IsDuplicate] [bit] NOT NULL,
[IsNotValid] [bit] NOT NULL,
[Time] [datetime] NOT NULL,
[ShortDate] [date] NOT NULL,
[Source] [nvarchar](350) NULL,
[Email] [nvarchar](350) NULL,
CONSTRAINT [PK_dbo.reports]
PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
这是我正在使用的 SQL 查询:
SELECT *
FROM [db].[dbo].[reports]
WHERE Source = 'name1'
AND ShortDate BETWEEN '2017-10-13' AND '2017-10-15'
据我所知,在不影响写入时间的情况下提高效率的最佳方法是在 Source
和 ShortDate
.
我喜欢这样,索引模式:
CREATE NONCLUSTERED INDEX [Source&Time]
ON [dbo].[reports]([Source] ASC, [ShortDate] ASC)
现在我们进入了让我完全迷失的棘手部分,上面的索引有时有效,有时一半有效,有时根本无效....
(不确定这是否重要,但目前 90% 的数据库行具有相同的源,尽管这种情况不会持续很长时间)
在下面的查询中,根本没有使用索引,我使用的是 SQL Server 2014,在执行计划中它说它只使用聚集索引扫描:
SELECT * FROM [db].[dbo].[reports] WHERE Source = 'name1' AND ShortDate BETWEEN '2017-10-10' AND '2017-10-15'
在这个查询中,索引根本没有被使用,尽管我从 SQL 服务器得到一个建议,创建一个日期在前,源在后的索引。 .我读到索引应该按查询的顺序制作?它还说要包含我 selecting 中的所有列,这是必须的吗?...我再次读到我应该只在索引中包含我正在搜索的列。
SELECT * FROM [db].[dbo].[reports] WHERE Source = 'name1' AND ShortDate = '2017-10-13'
SQL 服务器索引建议 -
/* The Query Processor estimates that implementing the following index could improve the query cost by 86.2728%. */ /* USE [db] GO CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>] ON [dbo].[reports] ([ShortDate], [Source]) INCLUDE ([id], [IsDuplicate], [IsNotValid], [Time], [Email]) GO */
现在我尝试使用索引 SQL 服务器建议我创建并且它有效,似乎它使用上面两个查询使用 100% 的非聚集索引。
我尝试使用此索引,但删除了包含的列,但它不起作用...似乎我必须在索引中包含我正在 selecting 的所有列?
顺便说一句,如果我包含所有列,它也可以在使用我创建的索引时使用。
总而言之:索引的顺序似乎并不重要,因为它在创建 Source + ShortDate
和 ShortDate + Source
但出于某种原因,必须包含所有列...(这将极大地影响对此 table 的写作?)
非常感谢您的阅读,我的目标是了解为什么会发生这种情况以及否则我应该做什么(不仅仅是解决方案,因为我还需要将其应用于其他项目)。
干杯:)
索引的包含列适用于您选择的列。
由于您执行 select *
(这不是好的做法),因此不会使用索引,因为它必须查找整个 table 才能获取列的值。
对于您的方案,我将删除默认的聚簇索引(如果有的话)并使用以下语句创建一个新的聚簇索引:
USE [db]
GO
CREATE CLUSTERED INDEX CIX_reports
ON [dbo].[reports] ([ShortDate],[Source])
GO
一般来说,您希望索引从选择性最高(即过滤掉最可能的记录)到选择性最低;如果列的基数较低,查询优化器可能会忽略它。
这很直观 - 如果您有一本 phone 的书,并且您正在寻找名为 "smith" 的人,首字母为 "A",您希望从搜索开始首先是"smith",然后是"A",而不是所有首字母为"A"的人,然后过滤掉那些叫"Smith"的人。毕竟,概率是每 26 个人中就有一个人拥有最初的 "A".
因此,在您的示例中,我猜您在短日期内具有广泛的值 - 因此这是查询优化器试图过滤掉的第一列。你说你在 "source" 中有几个不同的值,所以查询优化器可能决定忽略它;在那种情况下,该索引中的第二列也没有用。
索引中 where 子句的顺序无关紧要 - 您可以交换它们并获得完全相同的结果,因此查询优化器会忽略它们。
编辑:
所以,是的,制作索引。假设您有一堆卡片需要排序 - 在您的第一个 运行 中,您想要移除尽可能多的卡片。假设它全部均匀分布——如果你有 1000 个单独的 short_dates 超过一百万行,这意味着如果你的第一个 运行 从 short_date 开始,你最终会得到 1000 个项目;如果按来源排序,则有 100000 行。
SQL 服务器中的索引部分是长期经验(以及许多小时的挫折)的专有技术,部分是黑魔法。不要因为太多而自责 - 这就是像 SO 这样的地方的理想选择 - 大量的大脑,大量的优化经验,你可以利用。
I read that the index should be made by the order the query is?
如果你读到这个 - 它绝对 NOT TRUE - 列的顺序 是 相关的 - 但以不同的方式:仅当您在查询的索引定义中指定 n 最左边的列 时,才会考虑复合索引(由多列组成)。
经典示例:一本 phone 的书,其索引为(城市、姓氏、名字)。这样的索引可能会用到:
- 在其
WHERE
子句中指定所有三列的查询中 - 在使用
city
和lastname
的查询中(查找 "Detroit" 中的所有 "Miller") - 或在仅按城市过滤的查询中
但如果您只想搜索 firstname
..... 那是,则可以永远使用它您需要了解的有关复合索引的技巧。但是,如果您总是使用索引中的所有列,那么它们的顺序通常并不真正相关 - 查询优化器会为您处理。
至于 包含的列 - 那些 仅 存储在非聚集索引的叶级别 - 它们是 NOT 是索引搜索结构的一部分,您不能为 WHERE
子句中包含的那些列指定过滤器值。
这些包含的列的主要好处是:如果您在非聚集索引中搜索,最后您实际上找到了您要查找的值 - 此时您有什么可用的?非聚集索引将存储非聚集索引定义中的列(ShortDate
和 Source
),并且它将存储 聚集键 (如果您有一个- 你 应该!) - 但别无其他。
所以在这种情况下,一旦找到匹配项,并且您的查询需要 table 的所有内容,SQL 服务器必须执行以下操作称为 键查找 (通常也称为 书签查找 ),其中它获取聚集键然后执行 Seek 对聚簇索引的操作,以获取包含您要查找的所有值的实际数据页。
如果您的索引中包含列,则非聚集索引的叶级页面包含
- 非聚集索引中定义的列
- 集群键列
- 您的
INCLUDE
语句中定义的所有其他列
如果这些列 "cover" 您的查询,例如提供您的查询需要的所有值,然后 SQL 服务器一旦找到您在非聚集索引中搜索的值就完成了 - 它可以从非聚集索引的叶级页面中获取它需要的所有值,并且它不需要对聚类索引进行另一次(昂贵的)键查找以获取实际值。
因此,尝试总是明确指定只有那些你真正需要的列在你的SELECT
中可能是有益的- 在这种情况下,您 可能能够 创建一个有效的 覆盖索引 为您的 SELECT
提供所有值 - 始终使用SELECT *
使这变得非常困难或几乎不可能.....