【问题标题】:How can I get the last record for a given id in the fastest way? (MS SQL)如何以最快的方式获取给定 id 的最后一条记录? (微软 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-existssqlshack.com/when-to-use-temporary-tables-vs-table-variables
  • 你是如何测量时间的?
  • 请为您的表索引添加表类型的定义和create index 语句。在性能优化方面,这些细节至关重要。

标签: sql sql-server tsql query-performance


【解决方案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 必须执行向后扫描/查找时,它不能使用并行计划。但是,您可以尝试ascdescsequenceId 列的排序;很有可能,这对您的特定情况没有影响。

无论索引如何,您都可能需要将表变量替换为临时表。根据您使用的 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!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-15
      • 2020-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-23
      • 1970-01-01
      相关资源
      最近更新 更多