【问题标题】:Does a SELECT COUNT(*) query have to do a full table scan?SELECT COUNT(*) 查询是否必须进行全表扫描?
【发布时间】:2023-04-03 19:17:02
【问题描述】:

获取表中所有行数的查询是否必须执行全表扫描,或者 SQL Server 是否在某处维护行数?

SELECT COUNT(*) FROM TABLE_NAME;

表TABLE_NAME 有一个主键,因此是一个聚集索引,如下所示:

CREATE TABLE TABLE_NAME
(
  Id int PRIMARY KEY IDENTITY(1, 1),
  Name nvarchar(50) NOT NULL
);

我使用的是 Microsoft SQL Server 2014。

【问题讨论】:

  • 表有主键吗?
  • 不,它会查找表中的每一行,msql server 不会维护一个计数器
  • 服务器确实在sys.partitions 中维护了行计数elsewhere。您编写的查询不会使用它。查询优化器可以决定使用表扫描还是扫描索引

标签: sql-server database sql-server-2014


【解决方案1】:

服务器将始终读取所有记录(如果有索引,则它将扫描整个索引)以计算行数。只要你在做SELECT COUNT(*) FROM Table,你就无法逃避这个。

如果您的表有聚集索引,您可以 change your query 进行“幕后”查询以检索计数,而无需实际获取记录:

SELECT OBJECT_NAME(i.id) [Table_Name], i.rowcnt [Row_Count]
FROM sys.sysindexes i WITH (NOLOCK)
WHERE i.indid in (0,1)
ORDER BY i.rowcnt desc

如果您正在查找 近似 条记录,您还可以使用以下查询:

SELECT 
    TableName = t.NAME,
    SchemaName = s.Name,
    [RowCount] = p.rows,
    TotalSpaceMB = CONVERT(DECIMAL(18,2), SUM(a.total_pages) * 8 / 1024.0), 
    UsedSpaceMB = CONVERT(DECIMAL(18,2), SUM(a.used_pages) * 8 / 1024.0),
    UnusedSpaceMB = CONVERT(DECIMAL(18,2), (SUM(a.total_pages) - SUM(a.used_pages)) * 8 / 1024.0)
FROM 
    sys.tables t
    INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
    INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
    INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
    LEFT OUTER JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE 
    t.NAME NOT LIKE 'dt%' 
    AND t.is_ms_shipped = 0
    AND i.OBJECT_ID > 255 
GROUP BY 
    t.Name, 
    s.Name, 
    p.Rows
ORDER BY 
    TotalSpaceMB DESC

这将显示非系统表及其计算的(不准确的)行数和数据大小的总和(以及它们可能具有的任何索引),相对较快,无需检索记录。

【讨论】:

    【解决方案2】:

    当 SQL Server 执行 SELECT COUNT(*) 之类的查询时,SQL Server 将使用最窄的 non-clustered index 来计算行数。如果表没有non-clustered index,则必须扫描表。

    如果您的表有 clustered index,您可以更快地获得计数。

    【讨论】:

    • 那么,如果表有一个主键,因此有一个聚集索引,SQL Server 是否进行全表扫描?然后它如何获得行数?
    • 如果表有主键,则不会进行全表扫描。供您参考dbatipster.blogspot.com/2009/08/get-row-counts-fast.html
    • @WaterCoolerv2 否 - 不要假设主键和聚集索引始终相同。
    【解决方案3】:
    SELECT COUNT(*) FROM TABLE_NAME;
    

    进行全表扫描。

    优化可以参考this。

    【讨论】:

      【解决方案4】:

      您可以按照以下方式进行。我猜它的性能更好。

      SELECT COUNT(1) FROM TABLE_NAME 
      

      【讨论】:

      • 神话 - 你会得到完全相同的查询计划。
      • 如果我选择 count(*) 和 count(1),我可以看到查询输出时间的差异
      猜你喜欢
      • 1970-01-01
      • 2021-08-29
      • 2017-10-22
      • 2010-10-12
      • 2011-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      相关资源
      最近更新 更多