【问题标题】:Slow select performance on a table表上的选择性能缓慢
【发布时间】:2018-05-09 08:49:41
【问题描述】:

我正在使用 SQL Server,我有一张这样的表

CREATE TABLE dbo.CompanyRolesExpanded (
  StaticId uniqueidentifier NOT NULL,
  UserId uniqueidentifier NULL,
  UserGroupId uniqueidentifier NULL,
  CompanyId uniqueidentifier NULL,
  CompanyGroupId uniqueidentifier NULL,
  CompanyAccessUnitRole uniqueidentifier NULL,
  PRIMARY KEY CLUSTERED (StaticId)
)
GO

目前,此表大约有 300 万行。 像这样的简单选择大约需要 30 秒

SELECT  UserId,UserGroupId
       ,CompanyId,CompanyGroupId 
       ,CompanyAccessUnitRole                   
FROM CompanyRolesExpanded

有什么方法可以改进吗?

【问题讨论】:

  • 如果不需要选择整个表,那么使用TOP
  • 鉴于没有连接和谓词,您是否可能只需要更快的网络连接或更快的磁盘?这感觉更像是一个 DBA 问题,而不是一个编程问题。你在哪里运行查询?结果是否通过某种网络链接传播?您真的需要恢复所有 300 万行吗?
  • 没有 WHERE 子句的查询?你真的需要找回全部 300 万吗?
  • 可能是客户端(可能是 SSMS)在显示结果而不是服务器处理请求时花费了很长时间。执行SELECT INTO 并检查是否需要类似的时间。
  • 添加到@EzLo:GUID 是二进制模式,但显示为人类消费者的字符串。内存中的 16 个字节必须转换为由 32 个有意义的字符加上一些连字符组成的字符串。 300 万行中的 5 个 GUID 需要这样做 15 次。无论您在这里做什么:这不是一个相关的用例。

标签: sql sql-server performance


【解决方案1】:

在这种情况下,从性能的角度来看,我不认为 guid 是帐篷中的长杆。从远程服务器运行下面的 3M 行选择的 PowerShell 测试,结果显示 int 测试平均快 10% 左右。假设您的环境中的结果相似,则 int 转换为 27 秒,而 guid 转换为 30 秒。我观察到大部分时间是由于大型结果集的客户端 CPU 处理。

这并不是说不需要考虑 guid,尤其是在单磁盘旋转媒体存储上,但我想明确指出,问题在于大型结果集而不是数据类型。

$connectionString = "Data Source=YourServer;Initial Catalog=tempdb;Integrated Security=SSPI;Application Name=PerformanceTestScript";

$guidSetupScript = @"
CREATE TABLE dbo.Example (
  StaticId uniqueidentifier NOT NULL,
  UserId uniqueidentifier NULL,
  UserGroupId uniqueidentifier NULL,
  CompanyId uniqueidentifier NULL,
  CompanyGroupId uniqueidentifier NULL,
  CompanyAccessUnitRole uniqueidentifier NULL,
  PRIMARY KEY CLUSTERED (StaticId)
);
WITH 
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
    ,t10m AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a CROSS JOIN t1k AS b CROSS JOIN t1k AS c CROSS JOIN t10 AS d)
INSERT INTO dbo.Example WITH(TABLOCKX) (
      StaticId
    , UserId
    , UserGroupId
    , CompanyId
    , CompanyGroupId
    , CompanyAccessUnitRole
    )
SELECT
      NEWID()
    , NEWID()
    , NEWID()
    , NEWID()
    , NEWID()
    , NEWID()
FROM t10m
WHERE num <= 3000000;
"@

$intSetupScript = @"
CREATE TABLE dbo.Example (
  StaticId int NOT NULL,
  UserId int NULL,
  UserGroupId int NULL,
  CompanyId int NULL,
  CompanyGroupId int NULL,
  CompanyAccessUnitRole int NULL,
  PRIMARY KEY CLUSTERED (StaticId)
);
WITH 
     t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
    ,t1k AS (SELECT 0 AS n FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
    ,t10m AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS num FROM t1k AS a CROSS JOIN t1k AS b CROSS JOIN t1k AS c CROSS JOIN t10 AS d)
INSERT INTO dbo.Example WITH(TABLOCKX) (
      StaticId
    , UserId
    , UserGroupId
    , CompanyId
    , CompanyGroupId
    , CompanyAccessUnitRole
    )
SELECT
      num
    , num
    , num
    , num
    , num
    , num
FROM t10m
WHERE num <= 3000000;
"@

try
{
    $values = [System.Array]::CreateInstance([System.Object], 6)
    $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
    $command = New-Object System.Data.SqlClient.SqlCommand
    $command.CommandTimeout = 0
    $connection.Open()
    $command.Connection = $connection

    #Guid setup
    $command.CommandText = "IF OBJECT_ID(N'dbo.Example') IS NOT NULL DROP TABLE dbo.Example;"
    [void]$command.ExecuteNonQuery()
    $command.CommandText = $guidSetupScript
    [void]$command.ExecuteNonQuery()

    #guid test
    $testSw = [System.Diagnostics.StopWatch]::StartNew()
    Write-Host "Starting Guid test."
    $command.CommandText = "SELECT * FROM dbo.Example;"
    $reader = $command.ExecuteReader()
    while($reader.Read()) {
        $values = $reader.GetValues($values)
    }
    $reader.Close()
    $testSw.Stop()
    Write-Host "Guid test duration was $($testSw.Elapsed.ToString())"

    #int setup
    $command.CommandText = "IF OBJECT_ID(N'dbo.Example') IS NOT NULL DROP TABLE dbo.Example;"
    [void]$command.ExecuteNonQuery()
    $command.CommandText = $intSetupScript
    [void]$command.ExecuteNonQuery()

    #int test
    $testSw = [System.Diagnostics.StopWatch]::StartNew()
    Write-Host "Starting int test."
    $command.CommandText = "SELECT * FROM dbo.Example;"
    $reader = $command.ExecuteReader()
    while($reader.Read()) {
        $values = $reader.GetValues($values)
    }
    $reader.Close()
    $testSw.Stop()
    Write-Host "Int test duration was $($testSw.Elapsed.ToString())"

    $connedtion.Close()

}
catch [Exception]
{
    throw
}

【讨论】:

    【解决方案2】:

    虽然问题的完整上下文(如执行计划、索引)未知,但我很想列出与 GUID 相关的相当大的失败列表作为我的答案。

    表中的所有列都有一个 GUID。

      StaticId uniqueidentifier NOT NULL,
      UserId uniqueidentifier NULL,
      UserGroupId uniqueidentifier NULL,
      CompanyId uniqueidentifier NULL,
      CompanyGroupId uniqueidentifier NULL,
      CompanyAccessUnitRole uniqueidentifier NULL
    

    source 中引用作者偏爱 GUID 的缺点

    GUID 缺点

    1. 比传统的 4 字节索引值大 4 倍之多;这可能会产生严重的性能和存储影响,如果 你不小心
    2. 在 userid='{BAE7DF4-DDF-3RG-5TY3E3RF456AS10}' 的地方调试很麻烦
    3. 生成的 GUID 应该是部分顺序的,以获得最佳性能(例如,SQL 2005 上的 newsequentialid())并启用 聚集索引

    与使用 say int 作为 Key 相比,您的数据将分布在更多页面上,并且会有更多的物理读取。

    如果您执行大量插入/更新/删除,您的索引将高度碎片化。之所以如此,是因为 GUID 是随机生成的,并且更新索引以按顺序组织它们需要引擎费力。

    我敢打赌,您的索引需要重建。这是 an article,它将 GUID 与 INT 列索引进行比较,并反映 GUID 比 INT 慢,但可以改进并与索引重建相当。

    如果您认为 GUID 是罪魁祸首,我建议您将 bigint 视为一个选项

    【讨论】:

    • 这也是我的假设,Guids 是罪魁祸首,但我无法摆脱他们。我正在调查一个性能问题,我创建了这个表(而不是在运行时运行不同的连接和选择)来查看它的行为。
    • OP可以使用NEWSEQUENTIALID(),修复了一些问题(其实是比较重要的问题)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多