如何以最快的方式获取给定 id 的最后一条记录? (微软 SQL)
Posted
技术标签:
【中文标题】如何以最快的方式获取给定 id 的最后一条记录? (微软 SQL)【英文标题】:How can I get the last record for a given id in the fastest way? (MS SQL) 【发布时间】:2019-11-15 08:15:21 【问题描述】:我想寻求您的帮助。
我有一个如下所示的表格:
id | sequenceId
---------------
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
2 | 4
...
还有其他列,但现在不重要了。另一件事,密钥是一个密钥对 (id,sequenceId),它们在表中被索引。我想要的是获取给定 ID 的最后一行。 例如,如果 myId = 1 -> 给我 (1,3),myId = 2 -> 给我 (2,4) 记录等等。 在我的表中,有 500 个 id,每个 id 有 50 000 个序列 id,所以记录的大小是 500 * 50 000
我的查询:
SELECT
myId AS 'MyId',
MAX(sequenceId) AS 'SequenceId'
FROM myTable
WHERE myId in (SELECT myId from @MyIds)
GROUP BY(myId)
OPTION (RECOMPILE);
不幸的是,这并没有我想要的那么快。在我的尝试中,@MyIds 包含所有 id,1 - 500,在这种情况下,执行时间约为 1 秒。但我想让它更快。
你知道我怎样才能让它更快吗?也许另一个查询比我使用的更好?
感谢您的回答。
兄弟。
【问题讨论】:
桌子上有索引吗? OP 的文本:还有其他列,但现在不重要了。另一件事,key 是一个 keypair (id,sequenceId),它们在表中被索引。 您可以尝试 EXISTS 或 JOIN 而不是 IN。然而,对于这样简单的查询,优化器可能会选择相同的执行路径。您也可以尝试使用临时表而不是表变量。以下是一些详细介绍这两个主题的文章:explainextended.com/2009/06/16/in-vs-join-vs-exists 和 sqlshack.com/when-to-use-temporary-tables-vs-table-variables 你是如何测量时间的? 请为您的表索引添加表类型的定义和create index
语句。在性能优化方面,这些细节至关重要。
【参考方案1】:
您的查询是正确且相对最优的;除了用索引临时表替换表变量之外,您可能不会通过以其他方式重写它来获得任何改进。
性能优化通常与索引有关。根据id
列是否被索引,以下选项之一应该会有所帮助:
create index [IX_mytable_myid_sequenceid] on dbo.mytable (myid, sequenceid desc);
如果表上的聚集索引是在myId
列上创建的,那么可以节省一点空间:
create index [IX_mytable_sequenceid] on dbo.mytable (sequenceid desc);
排序顺序很重要,因为不幸的是,当 SQL Server 必须执行向后扫描/查找时,它不能使用并行计划。但是,您可以尝试asc
和desc
对sequenceId
列的排序;很有可能,这对您的特定情况没有影响。
无论索引如何,您都可能需要将表变量替换为临时表。根据您使用的 SQL Server 版本,基数估计器假定表变量有 1 行或 100 行。如果您的数据量未达到估计值,则执行计划将是次优的。所以代码应该是这样的:
create table #list (Id int primary key);
insert into #list (Id)
-- Assuming there are no duplicates, otherwise add DISTINCT
select MyId from @MyIds;
SELECT
t.myId AS 'MyId',
MAX(t.sequenceId) AS 'SequenceId'
FROM myTable t
inner join #list l on l.Id = t.myId
GROUP BY t.myId
-- OPTION (RECOMPILE);
是否应该离开option
子句取决于性能。
【讨论】:
我看不出没有id
列的索引将如何使OP 的查询受益,因为他没有按sequenceid
过滤。
我用“INNER JOIN”试了一下,执行时间比较慢,所以我用“IN”
@EzLo, "如果表上的聚集索引是在 id 列上创建的"。
@RogerWolf,是的,即使使用Id
聚集,它也会使用聚集索引而不是仅使用sequenceId
的非聚集索引,但也许我错过了一些东西。
说得好。我的Plus1!请注意,您可以按降序返回行而不会丢失并行性;请参阅下面的帖子,了解如何避免对此类情况进行向后扫描。【参考方案2】:
首先,@MyIds 是一个表变量,不是吗?你如何声明这个?它被索引了吗?在上面添加主键:
DECLARE @MyIds TABLE (ID INT PRIMARY KEY)
其次,确保您的密钥在myId+sequenceId
而不是sequenceId+myId
第三,避免IN
子句有很多项目,这是一个瓶颈
这应该是你最好的选择:
SELECT myId MyId, MAX(sequenceId) SequenceId
FROM myTable t
WHERE EXISTS (SELECT TOP 1 'X' X from @MyIds m WHERE m.myId = t.myId)
GROUP BY myId
你也可以尝试分组后强制过滤,试试看:
SELECT *
FROM (
SELECT TOP (9223372036854775807) myId MyId, MAX(sequenceId) SequenceId
FROM myTable t
GROUP BY myId
ORDER BY myId
) T
WHERE EXISTS (SELECT TOP 1 'X' X from @MyIds m WHERE m.myId = t.myId)
【讨论】:
@MyIds 是在我正在使用的存储过程中创建的 TYPE。 我已经尝试过这种方式,但不幸的是,性能并没有那么好。【参考方案3】:您可以尝试如下 INNER JOIN-
SELECT
A.myId AS 'MyId',
MAX(A.sequenceId) AS 'SequenceId'
FROM myTable A
INNER JOIN @MyIds B
ON A.myId = B.myId
GROUP BY(A.myId)
以下脚本将为您返回每个 myID 的最大序列值-
SELECT * FROM
(
SELECT myId,sequenceId,
ROW_NUMBER() OVER(PARTITION BY myId ORDER BY sequenceId DESC) RN
FROM myTable
)A
WHERE RN = 1
【讨论】:
我用“INNER JOIN”试了一下,执行时间比较慢,所以我用“IN”【参考方案4】:select
id.id as id,
seq as maxSequence,
data.someData as someData
from
(select id, max(sequenceId) as seq from #tab group by id) id
left join #tab data on id.id = data.id and id.seq = data.sequenceId
【讨论】:
【参考方案5】:我会推荐以下内容:
select i.myId,
(select max(t.sequenceId)
from myTable t
where t.myId = i.myId
)
from @MyIds i;
然后,为了提高性能,您需要在myTable(myId, sequenceId desc)
上建立索引。
【讨论】:
索引不应该是myTable(myId, sequenceId desc)
以在myId
的第一场比赛中获得最大的sequenceId
?
@HABO 。 . .我认为这是一个更好的选择。即使使用升序索引,SQL Server 也可能很智能。【参考方案6】:
如前所述 - 如果您在 myId 上有索引,则您的查询 应该 运行 sequenceId。列存储索引和/或batch mode processing 可以显着加快速度。如果您可以在索引中添加过滤器,那就更好了。内存优化表和/或其他对象也可以加快速度。说了这么多,让我介绍一种新的索引——Virtual Index
。您可以利用 RangeAB 或 Jeff Moden 的 FnTally。
使用 dbo.rangeAB 进行虚拟索引
首先进行快速热身。让我们创建一个查询,以升序和降序返回数字 1 到 10。让我们在没有索引的情况下使用并行执行计划。
SELECT r.RN, r.Op
FROM dbo.rangeAB(1,10,1,1) AS r
ORDER BY r.RN
OPTION (QUERYTRACEON 8649)
返回:
RN Op
-------------------- --------------------
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
执行计划:
看 ^^^ 没有排序!!!因此,对于降序 ORDER BY,您的查询如下所示:
-- Last 3 Numbers - no index, no sort + Descending Order + Parallelism (if you want it)
SELECT TOP (3) r.Op
FROM dbo.rangeAB(1,10,1,1) AS r
ORDER BY r.RN ASC
--OPTION (QUERYTRACEON 8649);
这里我们有一个虚拟的 FORWARD-ORDER 扫描,它以降序返回行。 不需要索引,不需要排序运算符! 这不是技巧,让我们调用两次函数并做一些需要排序的事情(从单独的函数调用中按两列分组、连接、传统聚合和我们'将以按窗口排名函数排序的 ORDER BY(不)排序来结束它...
DECLARE @rows INT = 10;
SELECT
RN1 = r.RN,
RN1_DESC = @rows+1-r.RN,
RN2 = r2.RN,
RN1_Low = MIN(r.RN),
RN1_High = MAX(r.RN),
RN1_Avg = AVG(r.RN)
FROM dbo.rangeAB(1,@rows,1,1) AS r
LEFT JOIN dbo.rangeAB(1,3,1,1) AS r2 ON r.RN = r2.RN
GROUP BY r.RN, r2.RN
ORDER BY DENSE_RANK() OVER (ORDER BY r.RN);
返回:
RN1 RN1_DESC RN2 RN1_Low RN1_High RN1_Avg
----- ----------- -------- ---------- ---------- --------------------
1 10 1 1 1 1
2 9 2 2 2 2
3 8 3 3 3 3
4 7 NULL 4 4 4
5 6 NULL 5 5 5
6 5 NULL 6 6 6
7 4 NULL 7 7 7
8 3 NULL 8 8 8
9 2 NULL 9 9 9
10 1 NULL 10 10 10
结果集并不是为了有意义,而是我感兴趣的执行计划;我们来看一下。
返回您的查询
-- Sample data
DECLARE @table TABLE (id INT NOT NULL, sequenceId INT NOT NULL)--, INDEX xxx(id,sequenceId))
INSERT @table VALUES(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(2,4)
SELECT r.RN, sequenceId = MAX(t.sequenceId)
FROM
(
SELECT MIN(t.id), MAX(t.id), MIN(t.sequenceId), MAX(t.sequenceId)
FROM @table AS t
) AS mm(Mn,Mx,Mns,Mxs)
CROSS APPLY dbo.rangeAB(mm.Mn,mm.Mx,1,1) AS r
CROSS APPLY dbo.rangeAB(mm.Mns,mm.Mxs,1,1) AS r2
JOIN @table AS t
ON r.RN = t.id AND r2.RN = Mxs
GROUP BY r.RN
OPTION (QUERYTRACEON 8649);
无索引、无排序、无 I/O、无并行度损失(无论方向如何)且无 RBAR!
【讨论】:
以上是关于如何以最快的方式获取给定 id 的最后一条记录? (微软 SQL)的主要内容,如果未能解决你的问题,请参考以下文章