【问题标题】:How to SELECT COUNT from tables currently being INSERT?如何从当前正在插入的表中选择 COUNT?
【发布时间】:2015-10-12 03:48:23
【问题描述】:

您好,考虑在表 TABLE_A 上运行 INSERT 语句,这需要很长时间,我想看看它进展如何。

我尝试的是打开一个新会话(SSMS 中的新查询窗口),而长时间运行的语句仍在处理中,我运行了查询

SELECT COUNT(1) FROM TABLE_A WITH (nolock)

希望我每次运行查询时它会立即返回行数,但测试结果是偶数(nolock),但它只在INSERT语句完成后返回。

我错过了什么?我是否也将 (nolock) 添加到 INSERT 语句中?或者这是无法实现的?


(编辑) 好的,我找到了我错过的东西。如果您首先使用 CREATE TABLE TABLE_A,然后 INSERT INTO TABLE_A,则 SELECT COUNT 将起作用。如果您使用 SELECT * INTO TABLE_A FROM xxx,而没有先创建 TABLE_A,则以下任何一项都不会起作用(甚至 sysindexes 也不行)。

【问题讨论】:

  • 单个 INSERT 是一个原子语句。它要么插入了所有行,要么没有插入。交易中间没有可用的计数。
  • 我对 With (Nolock) 的了解是,它返回为 DML 语句锁定表之前拍摄的快照。所以在 DML 完成之前,它不会返回最新的结果。
  • @AshimDas 这无助于逐步计算已插入的行数。
  • 永远不要在插入时使用 NOLOCK。这是 sql server 中非常绝对的事情之一。在插入、更新或删除时使用 NOLOCK 是任何时候都行不通的,这确实没有例外。它可能会破坏您的索引并导致各种问题。
  • insert 的上下文是什么?它是否包含在 transaction 中和/或来自 select?因为下面有证据表明一个人可以从某些正在进行的inserts 中获得部分count

标签: sql sql-server sql-server-2012


【解决方案1】:

简答:你不能这样做。

更长的答案:单个 INSERT 语句是 atomic 操作。因此,查询要么插入了所有行,要么没有插入任何行。因此,您无法计算它的进展程度。

更长的答案:马丁史密斯为您提供了实现您想要的东西的方法。当然,您是否仍然想这样做取决于您。就我个人而言,如果您确实需要跟踪此类事情的进度,我仍然更喜欢以可管理的批次插入。所以我会将 INSERT 重写为多个较小的语句。根据您的实施,这可能是一件微不足道的事情。

【讨论】:

  • 谢谢,很高兴知道。但是,当您考虑到原子操作需要这么长时间才能完成时,这确实违反直觉,它必须逐块慢慢地做事......仍然无法看到那里发生了什么。
  • 很可能,取决于您的观点。但是能够确定进度将违反原子性规则。顺便说一句,您几乎肯定不想使用游标,如果使用不当,它们可能会非常慢并且会影响性​​能。不能批量插入,一次插入 100 条或 100 条记录吗?
  • 嗯,我想我会尝试分批进行,但问题是我拥有的一些 INSERT 可以做 avg。 1000 记录/秒,但有些像 5 记录/秒。为了确定合适的批量大小,我必须测试每个 INSERT 并做一个 CASE,这不是我想要的。
  • 每秒 5 次 INSERT 似乎非常慢,除非数据是数兆字节,否则这看起来像是性能问题。
  • 我的 INSERT 包括使用 OPENROWSET 读取 Excel 等文件,我想这就是原因。
【解决方案2】:

如果您使用的是 SQL Server 2016,live query statistics 功能可以让您实时查看插入进度。

下面的屏幕截图是在将 1000 万行插入到具有聚集索引和单个非聚集索引的表中时截取的。

它显示在聚集索引上的插入已完成 88%,这将是一个排序运算符,以在插入 NCI 之前将值转换为非聚集索引键顺序。这是一个阻塞运算符,在所有输入行都被消耗完之前,排序不能输出任何行,因此左侧的运算符完成率为 0%。

关于你在NOLOCK的问题

测试很简单

连接 1

USE tempdb

CREATE TABLE T2
  (
     X INT IDENTITY PRIMARY KEY,
     F CHAR(8000)
  );

WHILE NOT EXISTS(SELECT * FROM   T2 WITH (NOLOCK))
  LOOP:

SELECT COUNT(*) AS CountMethod FROM   T2 WITH (NOLOCK);

SELECT rows FROM   sysindexes WHERE  id = OBJECT_ID('T2');

RAISERROR ('Waiting for 10 seconds',0,1) WITH NOWAIT;

WAITFOR delay '00:00:10';

SELECT COUNT(*) AS CountMethod FROM   T2 WITH (NOLOCK);

SELECT rows FROM   sysindexes WHERE  id = OBJECT_ID('T2');

RAISERROR ('Waiting to drop table',0,1) WITH NOWAIT

DROP TABLE T2 

连接 2

use tempdb;

--Insert 2000 * 2000 = 4 million rows
WITH T
     AS (SELECT TOP 2000 'x' AS x
         FROM   master..spt_values)
INSERT INTO T2
            (F)
SELECT 'X'
FROM   T v1
       CROSS JOIN T v2
OPTION (MAXDOP 1) 

示例结果 - 显示行数增加

SELECTNOLOCK 的查询允许脏读。它们实际上并没有使用任何锁并且仍然可以被阻止,它们仍然需要在表 (and on a heap it will also take a hobt lock) 上使用SCH-S(模式稳定性)锁。

唯一与SCH-S 不兼容的是SCH-M(模式修改)锁。大概您还在同一个事务中对表执行了一些 DDL(例如,可能在同一个事务中创建它)

对于大型插入的用例,其中近似的飞行结果很好,我通常只轮询sysindexes,如上所示从元数据中检索计数,而不是实际计算行数 (non deprecated alternative DMVs are available)

当插入具有wide update plan 时,您甚至可以看到它以这种方式依次插入到各种索引中。

如果表是在插入事务中创建的,则此sysindexes 查询仍将阻塞,因为OBJECT_ID 函数不会根据未提交的数据返回结果,而不管有效的隔离级别如何。有时可以通过使用 nolocksys.tables 获取 object_id 来解决此问题。

【讨论】:

  • 你的 conn1 不是在做insert then 等待,这意味着来自 conn2 的计数既是预期的,又与 OP 无关?我不知道inserttransaction 在锁定方面的优势,所以我欢迎教育!
  • 您可能是正确的,但您的测试并不准确。添加WAITFOR 与尝试在插入中读取值不同。它完全有可能产生相同的结果,但我不太确定(目前无法在此处测试,因为我无法使用 SQL Server)
  • @underscore_d - 只是因为它使演示更容易。其他答案中的论点是这是不可能的,因为它会违反 ACID。自动提交事务中的单个语句和显式 tran 之间的 ACID 属性没有区别。我当然可以生成具有相同行为的单个语句的示例。
  • 嗯,就像@davidg,我有兴趣在我回到办公桌后进行测试。
  • +1。与此同时,我还找到了另一个支持性讨论:ask.sqlservercentral.com/questions/44243/… 感谢您的课程!
【解决方案3】:

使用以下查询以秒为单位查找任何大型表或锁定表或正在插入的表的计数。只需替换您要搜索的表名即可。

SELECT
   Total_Rows= SUM(st.row_count)
FROM
   sys.dm_db_partition_stats st
WHERE
    object_name(object_id) = 'TABLENAME' AND (index_id < 2)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-10
    • 2018-12-24
    • 1970-01-01
    • 2010-10-11
    • 2013-02-02
    相关资源
    最近更新 更多