m__*_*m__ 8 sql-server clustered-index execution-plan sorting
这是主键中指定的排序顺序的衍生问题,但排序是在 SELECT 上执行的。
@Catcall关于存储顺序(聚集索引)和输出顺序的主题
很多人认为聚集索引可以保证输出的排序顺序。但这不是它的作用。它保证了磁盘上的存储顺序。 例如,请参阅此博客文章。
我已经阅读了 Hugo Kornelis 的博客文章,并了解到索引并不能保证 sql server 以特定顺序读取记录。然而,我很难接受我不能为我的场景假设这一点?
CREATE TABLE [dbo].[SensorValues](
[DeviceId] [int] NOT NULL,
[SensorId] [int] NOT NULL,
[SensorValue] [int] NOT NULL,
[Date] [int] NOT NULL,
CONSTRAINT [PK_SensorValues] PRIMARY KEY CLUSTERED
(
[DeviceId] ASC,
[SensorId] ASC,
[Date] DESC
) WITH (
FILLFACTOR=75,
DATA_COMPRESSION = PAGE,
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
ON [MyPartitioningScheme]([Date])
Run Code Online (Sandbox Code Playgroud)
我原来的查询是这样的:
SELECT TOP 1 SensorValue
FROM SensorValues
WHERE SensorId = 53
AND DeviceId = 3819
AND Date < 1339225010
ORDER BY Date DESC
Run Code Online (Sandbox Code Playgroud)
但我建议我也可以使用这个(阅读下面的解释):
SELECT TOP 1 SensorValue
FROM SensorValues
WHERE SensorId = 53
AND DeviceId = 3819
AND Date < 1339225010
Run Code Online (Sandbox Code Playgroud)
如您所见,我的表行很小(16 字节),而且我只有一个索引,一个聚集索引。在我的场景中,此时表包含 100.000.000 条记录(这很可能会增加十倍)。
当数据库服务器查询这个表时,它有两种方法可以找到我的行,要么寻找主键,然后读取并返回我在 desc 中的值。Date 的顺序,或者它必须做一个全表扫描。我的结论是,对所有这些记录进行全表扫描会太慢,因此数据库服务器将始终通过其主键查找表,从而返回按以下顺序排序的值Date DESC
ype*_*eᵀᴹ 16
让我试着解释为什么你不应该这样做,为什么你永远不应该假设 SQL 产品会以特定顺序返回结果集,除非你指定这样,无论索引 - 聚集或非聚集,B 树或R 树或 kd 树或分形树或 DBMS 正在使用的任何其他奇异索引。
您的原始查询告诉 DBMS 搜索SensorValues
表,查找与 3 个条件匹配的行,Date
按降序对这些行进行排序,仅保留其中的第一行 - 最后 - 仅选择并返回该SensorValue
列。
SELECT TOP 1 SensorValue
FROM SensorValues
WHERE SensorId = 53
AND DeviceId = 3819
AND Date < 1339225010
ORDER BY Date DESC ;
Run Code Online (Sandbox Code Playgroud)
这些是您提供给 DBMS 的非常具体的命令,每次运行查询时结果很可能是相同的(如果您有不止一行与条件匹配并且具有相同的maxDate
但不同,SensorValue
但让我们假设在接下来的对话中您的表中不存在这样的行)。
DBMS 是否必须按照我上面描述的方式运行此查询?不,当然不是,你知道。它可能不会读取表,而是从索引中读取。或者,如果它认为更好(更快),它可能会使用两个索引。或三个。或者它可能使用缓存的结果(不是 SQL Server,而是其他 DBMS 缓存查询结果)。或者它可能一次使用并行执行而不是下一次运行。或者...(添加影响执行和执行计划的任何其他功能)。
但可以保证的是,每次运行它都会返回完全相同的结果——只要没有插入、删除或更新行。
现在让我们看看你的建议是怎么说的:
SELECT TOP 1 SensorValue
FROM SensorValues
WHERE SensorId = 53
AND DeviceId = 3819
AND Date < 1339225010 ;
Run Code Online (Sandbox Code Playgroud)
这个查询告诉 DBMS 搜索SensorValues
表,找到符合 3 个条件的行,排序,不关心顺序,只保留一行,最后只选择并返回Date
按降序对这些行进行SensorValue
列。
所以,它基本上和第一个一样,只是它告诉你只想要一个符合条件的结果,你不在乎是哪个。
现在,我们可以假设它会因为聚集索引而总是给出相同的结果吗?
- 如果它每次都使用这个聚集索引,是的。
但它会用吗?
- 不。
为什么不?
- 因为可以。查询优化器可以在每次运行语句时自由选择执行路径。它认为当时适合该声明的任何路径。
但是使用聚集索引不是获得结果的最佳/最快方法吗?
- 不,并非总是如此。这可能是您第一次运行查询。第二次,它可能使用缓存的结果(如果 DBMS 有这样的功能,而不是 SQL Server *)。第 1000 次结果可能已从缓存中删除,并且那里可能存在另一个结果。假设您之前执行过这个查询:
SELECT TOP 1 SensorValue
FROM SensorValues
WHERE SensorId = 53
AND DeviceId = 3819
AND Date < 1339225010
ORDER BY Date ASC ; --- Notice the `ASC` here
Run Code Online (Sandbox Code Playgroud)
并且缓存的结果(来自上述查询)是另一个不同的结果,它仍然符合您的条件,但不是您(想要的)排序中的第一个。而且您已经告诉 DBMS 不要关心订单。
好的,所以只有缓存会影响这个?
- 不,还有很多其他的东西。
*:SQL Server 不缓存查询结果,但企业版确实具有高级扫描功能,该功能有点相似,因为并发查询可能会导致您获得不同的结果。不确定这到底是什么时候开始的。(谢谢@Martin Smith 的提示。)
我希望您确信您永远不应该依赖 SQL 查询将按特定顺序返回结果,除非您指定如此。并且永远不要使用TOP (n)
without ORDER BY
,除非您当然只想要结果中的 n 行并且您不关心返回哪些行。
归档时间: |
|
查看次数: |
4773 次 |
最近记录: |