【问题标题】:SSDT project builds much slower with MSBuild than in VS 2013与 VS 2013 相比,使用 MSBuild 构建 SSDT 项目的速度要慢得多
【发布时间】:2016-12-19 18:17:23
【问题描述】:

我注意到,我们的 SSDT 项目通过 TFS 构建定义构建的速度比通过本地开发盒上的 VS 2013 慢得多。

在构建服务器本身上,我可以在大约 2 分钟内通过 VS 2013 构建我们最大的 SSDT 项目。在同一台服务器上使用 MSBuild 在大约 16 分钟内从命令行构建完全相同的项目(即以与 TFS 构建定义构建 SSDT 项目相同的方式构建)

我已经收集了两种方法的 /verbosity:diagnostic 输出,但在我的一生中无法看到提供给 MSBuild 的参数有任何实质性差异。

在 VS 2013 中,配置为 Debug:AnyCPU。

我使用的 MSBuild 命令是:

msbuild /t:rebuild /p:Configuration=Debug /p:Platform=AnyCPU .sqlproj

我在更小的 SSDT 项目上尝试了相同的过程,但我在构建时间上也有类似的相对差异(例如,我在 VS 2013 中完成的另一个项目需要 10 秒,而通过 MSBuild 需要 70-80 秒)

我有最新版本的 VS 2013 (12.0.40629.00 Update 5) 和 SSDT (12.0.60629.0)

有没有其他人看到过这个问题,甚至有解决这个问题的方法?

迁移到 VS 2015 会解决问题吗?


已编辑以响应 cmets 并包含诊断信息 - 15-08-2016

感谢大家回复我的建议。

Ed,在针对 MSBuild 测试 VS 时,我使用了构建目标 Rebuild,我认为它是 Clean 和 Build 的组合。在 TFS 构建定义中,我将“清洁工作区”和“清洁构建”设置为 true,但“获取源”步骤的时间仅为 9 秒。

Steven,dbmdl 因素听起来很有希望。但是,在从解决方案中删除所有 dbmdl 文件后,我再次尝试了测试,但我仍然得到相同的时间(我验证了测试版本之间没有重新生成 dbmdl 文件)。此外,我在测试之前删除了所有 bin 和 obj 文件夹,只是为了消除 VS 构建缓存数据的任何可能性。

Cece,我已经有效地从这个问题中删除了 TFS,因为我在测试 VS 和 MSBuild 时遇到了构建时间差异。我想如果我能破解它,TFS 构建时间也会被破解。您对花费时间的构建步骤有一个很好的观点。我不想用太多信息淹没最初的帖子,但现在是时候了:-)。我使用 /clp:PerformanceSummary 选项来获取时间花费的细分。

在下面的第 1 节中,您可以看到几乎所有时间都花在了“SqlBuildTask”中。很高兴知道,但对查明问题没有太大帮助。

第 2 节和第 3 节是我使用 /verbosity:diagnostic 输出从两种构建方法中获得的信息

我看不出这两个版本之间有任何实质性差异。两者都必须使用此 DLL 来执行实际构建(MSBuild 显式声明了对此 DLL 的引用):

 C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\\Extensions\Microsoft\SQLDB\Dac\130\Microsoft.Data.Tools.Schema.Tasks.Sql.dll" 

------------- 第 1 节 -------------

(使用 /clp:PerformanceSummary 从 MSBuild 输出)

Project Performance Summary:
        82518 ms  P:\<snip>.sqlproj   1 calls
                  82518 ms  rebuild                                    1 calls

Target Performance Summary:
        0 ms  BuildOnlySettings                          2 calls
        0 ms  GetReferenceAssemblyPaths                  2 calls
        0 ms  GetSqlSymbolsPath                          1 calls
        0 ms  BeforeBuild                                2 calls
        0 ms  ResolveReferences                          2 calls
        0 ms  GetCopyToOutputDirectoryXamlAppDefs        2 calls
        0 ms  SqlStudioSourceFilesToCopy                 2 calls
        0 ms  AssignProjectConfiguration                 1 calls
        0 ms  BeforeClean                                2 calls
        0 ms  _CopySourceItemsToOutputDirectory          2 calls
        0 ms  Build                                      2 calls
        0 ms  GetCopyToOutputDirectoryItems              2 calls
        0 ms  Clean                                      2 calls
        0 ms  CleanPublishFolder                         2 calls
        0 ms  GetFrameworkPaths                          2 calls
        0 ms  GetTargetPath                              2 calls
        0 ms  _CleanGetCurrentAndPriorFileWrites         2 calls
        0 ms  _CopyFilesMarkedCopyLocal                  1 calls
        0 ms  AfterResolveReferences                     2 calls
        0 ms  PreXsdCodeGen                              2 calls
        0 ms  CopyFilesToOutputDirectory                 2 calls
        0 ms  GetNativeManifest                          1 calls
        0 ms  GetInstalledSDKLocations                   2 calls
        0 ms  IncrementalClean                           2 calls
        0 ms  PrepareForRun                              2 calls
        0 ms  _SplitProjectReferencesByFileExistence     2 calls
        0 ms  _SetupSqlBuildOutputs                      2 calls
        0 ms  BeforeResolveReferences                    2 calls
        0 ms  GetSqlTargetPath                           1 calls
        0 ms  Rebuild                                    1 calls
        0 ms  AfterClean                                 2 calls
        0 ms  AfterBuild                                 2 calls
        0 ms  ExpandSDKReferences                        2 calls
        0 ms  CleanStaticCodeAnalysis                    2 calls
        0 ms  BeforeRebuild                              1 calls
        0 ms  CheckRequiredProperties                    2 calls
        0 ms  GenerateSqlTargetFrameworkMoniker          2 calls
        0 ms  ResolveSDKReferences                       2 calls
        0 ms  AfterRebuild                               1 calls
        0 ms  ResolveArtifactReferences                  2 calls
        0 ms  _CopyOutOfDateSourceItemsToOutputDirectoryAlways   1 calls
        0 ms  _CheckForCompileOutputs                    2 calls
        0 ms  PrepareForBuild                            2 calls
        0 ms  CleanXsdCodeGen                            2 calls
       16 ms  _CheckForInvalidConfigurationAndPlatform   2 calls
       16 ms  AssignTargetPaths                          2 calls
       16 ms  CoreClean                                  2 calls
       31 ms  ResolveAssemblyReferences                  2 calls
       31 ms  SqlPrepareForRun                           2 calls
       31 ms  CleanReferencedProjects                    2 calls
       63 ms  PostBuildEvent                             2 calls
       63 ms  PreBuildEvent                              2 calls
       94 ms  CoreCompile                                2 calls
      625 ms  _SetupSqlBuildInputs                       2 calls
     6452 ms  ResolveProjectReferences                   2 calls
    81502 ms  SqlBuild                                   2 calls

Task Performance Summary:
        0 ms  AssignTargetPath                          26 calls
        0 ms  CallTarget                                 2 calls
        0 ms  FindAppConfigFile                          2 calls
        0 ms  ConvertToAbsolutePath                      2 calls
        0 ms  AssignProjectConfiguration                 1 calls
        0 ms  ReadLinesFromFile                          4 calls
        0 ms  WriteLinesToFile                           4 calls
        0 ms  Copy                                       6 calls
        0 ms  FindUnderPath                             14 calls
        0 ms  RemoveDuplicates                           6 calls
        0 ms  MakeDir                                    4 calls
       16 ms  SqlScriptDependenciesTask                  2 calls
       16 ms  Delete                                    12 calls
       31 ms  ResolveAssemblyReference                   2 calls
       31 ms  Message                                    8 calls
       94 ms  Csc                                        1 calls
      126 ms  Exec                                       4 calls
      516 ms  SqlModelResolutionTask                     2 calls
     6468 ms  MSBuild                                    7 calls
    81424 ms  SqlBuildTask                               2 calls

------------- 第 2 节 -------------

(在 VS 构建的 SqlBuildTask 阶段给出的诊断输出)

Target "SqlBuild" in file "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" from project "P:<snip>.sqlproj" (target "Build" depends on it):
    Building target "SqlBuild" completely.
    Output file "P:<snip>.dacpac" does not exist.
    Task "SqlBuildTask"
        Task Parameter:SqlTarget=P:<snip>.dacpac
        Task Parameter:ContributorArguments=ConfigurationName=Debug;
        Task Parameter:BuildContributors=;
        Task Parameter:DeploymentContributors=;
        Task Parameter:CreateScriptFileName=<snip>.sql
        Task Parameter:DacApplicationName=<snip>
        Task Parameter:DacDescription=<snip>
        Task Parameter:DacFile=P:<snip>\bin\Debug\
        Task Parameter:DacVersion=3.27.0.0
        Task Parameter:DatabaseName=<snip>
        Task Parameter:DatabaseSchemaProviderName=Microsoft.Data.Tools.Schema.Sql.Sql100DatabaseSchemaProvider
        Task Parameter:DefaultSchema=dbo
        Task Parameter:DeploymentScriptName=<snip>.sql
        Task Parameter:DeployToDatabase=True
        Task Parameter:ImplicitDllAssemblyName=<snip>
        Task Parameter:ImplicitDllFileName=P:<snip>.dll
        Task Parameter:ImplicitDllSymbolsFileName=P:<snip>.pdb
        Task Parameter:ImplicitDllGenerateSqlClrDdl=true
        Task Parameter:IntermediateDirectory=P:<snip>\obj\Debug\
        Task Parameter:ModelCollation=1033,CI
        Task Parameter:OutputDirectory=P:<snip>\bin\Debug\
        Task Parameter:
    Source= <snip - list of all the source files in the SSDT project - matches list for MSBuild builds>

        Task Parameter:
    SqlCmdVariables= <snip - same for both builds>
        Task Parameter:
    SqlReferencePath=
        C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                CopyLocal=false
                FrameworkFile=true
                FusionName=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
                ImageRuntime=v4.0.30319
                OriginalItemSpec=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                ReferenceSourceTarget=ResolveAssemblyReference
                ResolvedFrom=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                Version=4.0.0.0
        Task Parameter:SuppressTSqlWarnings=71562,71502,71558
        Task Parameter:ValidateCasingOnIdentifiers=true
        Task Parameter:CmdLineInMemoryStorage=false
        Task Parameter:DefaultCollation=SQL_Latin1_General_CP1_CI_AS
        Task Parameter:AnsiNullDefault=False
        Task Parameter:AnsiNulls=False
        Task Parameter:AnsiPadding=False
        Task Parameter:AnsiWarnings=False
        Task Parameter:ArithAbort=False
        Task Parameter:ConcatNullYieldsNull=False
        Task Parameter:QuotedIdentifier=False
        Task Parameter:NumericRoundAbort=False
        Task Parameter:RecursiveTriggersEnabled=False
        Task Parameter:DatabaseChaining=False
        Task Parameter:DatabaseState=ONLINE
        Task Parameter:CloseCursorOnCommitEnabled=False
        Task Parameter:DefaultCursor=GLOBAL
        Task Parameter:AutoClose=False
        Task Parameter:AutoCreateStatistics=True
        Task Parameter:AutoShrink=False
        Task Parameter:AutoUpdateStatistics=True
        Task Parameter:TornPageDetection=False
        Task Parameter:DatabaseAccess=MULTI_USER
        Task Parameter:Recovery=FULL
        Task Parameter:EnableFullTextSearch=False
        Task Parameter:DefaultFilegroup=PRIMARY
        Task Parameter:Trustworthy=True
        Task Parameter:AutoUpdateStatisticsAsynchronously=False
        Task Parameter:PageVerify=CHECKSUM
        Task Parameter:ServiceBrokerOption=DisableBroker
        Task Parameter:DateCorrelationOptimizationOn=False
        Task Parameter:Parameterization=SIMPLE
        Task Parameter:AllowSnapshotIsolation=False
        Task Parameter:ReadCommittedSnapshot=True
        Task Parameter:VardecimalStorageFormatOn=True
        Task Parameter:SupplementalLoggingOn=False
        Task Parameter:CompatibilityMode=100
        Task Parameter:IsChangeTrackingOn=False
        Task Parameter:IsChangeTrackingAutoCleanupOn=True
        Task Parameter:ChangeTrackingRetentionPeriod=2
        Task Parameter:ChangeTrackingRetentionUnit=Days
        Task Parameter:IsEncryptionOn=False
        Task Parameter:IsBrokerPriorityHonored=False
        Task Parameter:IncludeCompositeObjects=True
        Loading project references...
        Loading project files...
        Building the project model and resolving object interdependencies...
        Validating the project model...
        Writing model to P:<snip>\obj\Debug\Model.xml...

------------- 第三节 -------------

(在 MSBuild 构建的 SqlBuildTask 阶段给出的诊断输出)

Target "SqlBuild: (TargetId:68)" in file "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v12.0\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" from project "P:<snip>.sqlproj" (target "Build" depends on it):
Building target "SqlBuild" completely.
Output file "P:<snip>.dacpac" does not exist.
Using "SqlBuildTask" task from assembly "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\\Extensions\Microsoft\SQLDB\Dac\130\Microsoft.Data.Tools.Schema.Tasks.Sql.dll".
Task "SqlBuildTask" (TaskId:56)
  Task Parameter:SqlTarget=P:<snip>.dacpac (TaskId:56)
  Task Parameter:ContributorArguments=ConfigurationName=Debug; (TaskId:56)
  Task Parameter:BuildContributors=; (TaskId:56)
  Task Parameter:DeploymentContributors=; (TaskId:56)
  Task Parameter:CreateScriptFileName=<snip>.sql (TaskId:56)
  Task Parameter:DacApplicationName=<snip> (TaskId:56)
  Task Parameter:DacDescription=<snip> (TaskId:56)
  Task Parameter:DacFile=P:<snip>\bin\Debug\ (TaskId:56)
  Task Parameter:DacVersion=3.27.0.0 (TaskId:56)
  Task Parameter:DatabaseName=<snip> (TaskId:56)
  Task Parameter:DatabaseSchemaProviderName=Microsoft.Data.Tools.Schema.Sql.Sql100DatabaseSchemaProvider (TaskId:56)
  Task Parameter:DefaultSchema=dbo (TaskId:56)
  Task Parameter:DeploymentScriptName=<snip>.sql (TaskId:56)
  Task Parameter:DeployToDatabase=True (TaskId:56)
  Task Parameter:ImplicitDllAssemblyName=<snip> (TaskId:56)
  Task Parameter:ImplicitDllFileName=P:<snip>.dll (TaskId:56)
  Task Parameter:ImplicitDllSymbolsFileName=P:<snip>.pdb (TaskId:56)
  Task Parameter:ImplicitDllGenerateSqlClrDdl=true (TaskId:56)
  Task Parameter:IntermediateDirectory=P:<snip>\obj\Debug\ (TaskId:56)
  Task Parameter:ModelCollation=1033,CI (TaskId:56)
  Task Parameter:OutputDirectory=P:<snip>\bin\Debug\ (TaskId:56)
  Task Parameter:
      Source= <snip - list of all the source files in the SSDT project - matches list for VS builds>
  Task Parameter:
      SqlCmdVariables= <snip - same for both builds>
  Task Parameter:
      SqlReferencePath=
          C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                  CopyLocal=false
                  FrameworkFile=true
                  FusionName=mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
                  ImageRuntime=v4.0.30319
                  OriginalItemSpec=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                  ReferenceSourceTarget=ResolveAssemblyReference
                  ResolvedFrom=C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
                  Version=4.0.0.0 (TaskId:56)
  Task Parameter:SuppressTSqlWarnings=71562,71502,71558 (TaskId:56)
  Task Parameter:ValidateCasingOnIdentifiers=true (TaskId:56)
  Task Parameter:CmdLineInMemoryStorage=false (TaskId:56)
  Task Parameter:DefaultCollation=SQL_Latin1_General_CP1_CI_AS (TaskId:56)
  Task Parameter:AnsiNullDefault=False (TaskId:56)
  Task Parameter:AnsiNulls=False (TaskId:56)
  Task Parameter:AnsiPadding=False (TaskId:56)
  Task Parameter:AnsiWarnings=False (TaskId:56)
  Task Parameter:ArithAbort=False (TaskId:56)
  Task Parameter:ConcatNullYieldsNull=False (TaskId:56)
  Task Parameter:QuotedIdentifier=False (TaskId:56)
  Task Parameter:NumericRoundAbort=False (TaskId:56)
  Task Parameter:RecursiveTriggersEnabled=False (TaskId:56)
  Task Parameter:DatabaseChaining=False (TaskId:56)
  Task Parameter:DatabaseState=ONLINE (TaskId:56)
  Task Parameter:CloseCursorOnCommitEnabled=False (TaskId:56)
  Task Parameter:DefaultCursor=GLOBAL (TaskId:56)
  Task Parameter:AutoClose=False (TaskId:56)
  Task Parameter:AutoCreateStatistics=True (TaskId:56)
  Task Parameter:AutoShrink=False (TaskId:56)
  Task Parameter:AutoUpdateStatistics=True (TaskId:56)
  Task Parameter:TornPageDetection=False (TaskId:56)
  Task Parameter:DatabaseAccess=MULTI_USER (TaskId:56)
  Task Parameter:Recovery=FULL (TaskId:56)
  Task Parameter:EnableFullTextSearch=False (TaskId:56)
  Task Parameter:DefaultFilegroup=PRIMARY (TaskId:56)
  Task Parameter:Trustworthy=True (TaskId:56)
  Task Parameter:AutoUpdateStatisticsAsynchronously=False (TaskId:56)
  Task Parameter:PageVerify=CHECKSUM (TaskId:56)
  Task Parameter:ServiceBrokerOption=DisableBroker (TaskId:56)
  Task Parameter:DateCorrelationOptimizationOn=False (TaskId:56)
  Task Parameter:Parameterization=SIMPLE (TaskId:56)
  Task Parameter:AllowSnapshotIsolation=False (TaskId:56)
  Task Parameter:ReadCommittedSnapshot=True (TaskId:56)
  Task Parameter:VardecimalStorageFormatOn=True (TaskId:56)
  Task Parameter:SupplementalLoggingOn=False (TaskId:56)
  Task Parameter:CompatibilityMode=100 (TaskId:56)
  Task Parameter:IsChangeTrackingOn=False (TaskId:56)
  Task Parameter:IsChangeTrackingAutoCleanupOn=True (TaskId:56)
  Task Parameter:ChangeTrackingRetentionPeriod=2 (TaskId:56)
  Task Parameter:ChangeTrackingRetentionUnit=Days (TaskId:56)
  Task Parameter:IsEncryptionOn=False (TaskId:56)
  Task Parameter:IsBrokerPriorityHonored=False (TaskId:56)
  Task Parameter:IncludeCompositeObjects=True (TaskId:56)
  Creating a model to represent the project... (TaskId:56)
  Loading project references... (TaskId:56)
  Loading project files... (TaskId:56)
  Building the project model and resolving object interdependencies... (TaskId:56)
  Validating the project model... (TaskId:56)
  Writing model to P:<snip>\obj\Debug\Model.xml... (TaskId:56)
Done executing task "SqlBuildTask". (TaskId:56)

【问题讨论】:

  • 如果你做一个 clean 然后 build in vs,你什么时候得到?
  • 具体尝试关闭VS,从解决方案文件夹中删除dbmdl文件,然后重新构建。 VS 有一些性能优化,包括在 dbmdl 文件中缓存数据,但从命令行运行 msbuild 时不使用。
  • 您使用的是哪个版本的 TFS?您是说在 TFS 构建服务器上使用 MSBuild 命令行构建项目时,与使用 TFS 构建项目时获得相同的行为吗?日志中哪个步骤花费的时间最多?
  • 我发现了一个。似乎存在一个错误,即当通过 MSBUILD 构建时,sqlproj 文件中的重复引用会显着增加构建时间。我们最大的项目有大约 11,000 个实体。通过 MSBUILD 的构建时间在 30 到 80 分钟之间变化,而通过 VS 构建相同的项目需要 3-4 分钟。一旦我删除了 dup 引用(大约 40 个),构建时间就降到了一致的 9 分钟!我还没有时间尝试推断额外时间是否与重复次数或特定重复次数成正比。没关系 - 只需删除您的副本!
  • 除了删除 dup 之外,我所做的一项重要更改是将 /p:CmdLineInMemoryStorage=TRUE 添加到 MSBUILD 参数中。这是 sabin.io 的 Simon D'Morias 提出的(感谢 Simon)。我知道引入该标志是为了解决构建的内存问题,但它肯定也有助于我们在 TFS 中的构建时间。

标签: tfs msbuild sql-server-data-tools dacpac


【解决方案1】:

我对我们的大型 SSDT 项目和 TFS MSBUILD 参数进行了以下更改,这些更改通过 TFS 构建定义将构建时间带到了可解释的水平:

1) 将 /p:CmdLineInMemoryStorage=TRUE 添加到 MSBUILD 参数

这大大减少了我们的构建时间。通过添加此选项,我们的构建定义从 40 分钟缩短到 16 分钟。 (时间是预先删除重复引用)

2) 删除了 .sqlproj 文件中的所有重复引用

随着时间的推移,.sqlproj 文件成功地引用了某些文件 2 或 3 次。我们有大约 40 个重复的引用。这几乎可以肯定是由于我们许多分支之间的合并问题。 Visual Studio 没有抱怨这一点,并且构建得非常愉快,但是复制对我们在 TFS 中的构建时间产生了重大影响,当然它使用了 MSBUILD。

在 sqlproj 文件中查找重复引用

这是我用来在我们的 sqlproj 文件中查找重复项的 powershell 脚本(更改 $root 以指向包含您的 .sqlproj 文件的文件夹):

[Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null

$root = "C:\TFS"

$sqlprojFiles = Get-ChildItem -Path $root -Filter "*.sqlproj" -Recurse
$numFilesChecked=0

foreach ($f in $sqlprojFiles)
{
    $numFilesChecked++
    $fullName = $f.FullName

    $xml = [System.Xml.Linq.XDocument]::Load($fullName)
    $ns = $xml.Root.Name.Namespace;

    $itemGroups = $xml.Descendants() | Where-Object { $_.Parent.Name -eq $ns + "ItemGroup" };

    $dict = @{}
    $itemGroups.Attributes() | Where-Object { $_.Name -eq "Include"} `
        | % { if ($dict.ContainsKey($_.Value)) {$dict[$_.Value]++} else {$dict.Add($_.Value,1)} }

    $duplicates = $dict.Keys | Where-Object { $dict[$_] -gt 1} 

    $fullName

    if ($duplicates.Count -gt 0)
    {
        Write-Output "--------------------------------------------------------"
        $duplicates | % {$_+", "+$dict[$_]}
    }
    else
    {
        Write-Output "No duplicates found"
    }
}

"Files checked: $numFilesChecked"

我使用它的输出以文本方式手动编辑 .sqlproj 文件(即“卸载项目”,然后在 VS 中“编辑 .sqlproj”)

解释构建时间差异

现在我们的大型 sqlproj 在我们的构建服务器上构建只需 9 分钟,我认为 VS 和 MSBuild 之间的构建时间差异可以解释。

有人告诉我,VS 利用了 MSBuild 未从命令行使用的 SSDT 项目的预编译信息。我无法证实这一点,而且我当然没有在 VS 的“/verbosity:diagnostic”输出中看到任何证据(我可能错过了)。然而,VS 完成项目分析后在 VS 中构建大型项目需要 2 分钟,这表明它在构建过程中使用了缓存信息。 VS 需要几分钟才能完成对大型项目的分析。

MSBuild 构建时间(从命令行或 TFS 构建定义)现在持续 9 分钟。额外的 7 分钟几乎肯定会被 MSBuild 进程在每次构建时分析项目占用 - VS 似乎只做一次(或至少定期),然后在构建时利用缓存的信息。

【讨论】:

    【解决方案2】:

    对于那些经历过具有许多 dacpac 依赖项的项目的 SSDT 构建缓慢的人来说,还有一种选择:简化这些 dacpac 并使它们更小。

    关键假设是在意识到引用 dacpac 与部署它不同时做出的:它不应该像“正常”sqlproj 那样需要尽可能多的元数据。

    以下是可以使用 dacpac-references 完成的可能步骤,以大幅减少它们的大小和构建时间(除了 Mark 和其他人提到的选项)。

    显示的代码提示在 Powershell 中。

    DACPAC 内部

    DACPAC 是一个包含 XML 文件的 ZIP 存档。因此,您可以打开它并查看 model.xml,它是 项目 本身,表示为单个元数据和源文件。这个文件可能非常大(100MB+),正如您所知,XML 文件的大小会影响它的处理性能。 SSDT 对不同的 sql 元素生成非常详细的描述。

    所以要开始处理它,解压并加载源 XML:

    Expand-Archive -Path $dacpac -DestinationPath $expandedFolder | Out-Null;
    [xml] $dacpacXml = Get-Content "$expandedFolder/model.xml";
    
    $ns = New-Object System.Xml.XmlNamespaceManager($dacpacXml.value.NameTable);
    $ns.AddNamespace("dac", $dacpacXml.value.DocumentElement.NamespaceURI);
    

    元数据不用于引用

    授权

    我相信您的 TSQL 代码应该需要硬引用不同数据库中的此类对象,例如:

    • 用户、登录名、角色
    • 权限、角色成员、授权和执行上下文(用户/登录)信息

    所以删除它们

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlRoleMembership']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlPermissionStatement']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlLogin']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlUser']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlRole']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'Login']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'User']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'Authorizer']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'Schema']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    如果您将所有这些都保存在您的 sqlproj(用户、登录名)中,我建议您查看此方法并从 source 代码中删除 exploitation 级别的数据。

    加上敏感数据和签名

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Property[@Name = 'EncryptionPassword' or @Name = 'Password']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlCertificate' or @Type = 'SqlSymmetricKey']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlSignature']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    文件存储和分区信息

    如果您的分区表有很多分区,那么您的 dacpacs 包含每个分区的成百上千个元素。

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'DataCompressionOptions' or @Name = 'BoundaryValues']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'Filegroups']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'Filegroup']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'FilegroupForTextImage']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'PartitionScheme' or @Name = 'PartitionColumn']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    DML 和 DDL 对象

    也不应该从数据库外部引用对象

    • 触发器
    • 指标
    • 默认约束

    所以删除它们

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlIndex']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlDmlTrigger']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlIndexedColumnSpecification']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlDatabaseDdlTrigger']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlDefaultConstraint']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    来源

    虽然可以引用 SP,但它的源不是必需的 - 我们已经拥有它的所有元数据:参数被详细描述为具有类型信息的单独元素等。所以删除所有来源

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlView']/dac:Property[@Name = 'QueryScript']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlProcedure']/dac:Property[@Name = 'BodyScript']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlScriptFunctionImplementation']/dac:Property[@Name = 'BodyScript']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlAssemblyFile']/dac:Property[@Name = 'Source']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlComputedColumn']/dac:Property[@Name = 'ExpressionScript']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    

    以及从这些来源构建产生的依赖项

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'BodyDependencies']", $ns) | % { write-verbose $_.ParentNode.Name; $_.ParentNode.removechild($_); } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'QueryDependencies' or @Name = 'ExpressionDependencies']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'DynamicObjects']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'AssemblySources']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlQueue']/dac:Relationship", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlContract']/dac:Relationship[@Type = 'Messages']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    注释

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Annotation[@Type = 'SysCommentsObjectAnnotation']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Relationship[@Name = 'BoundTargets']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Annotation[@Type = 'PersistedResolvableAnnotation']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlExtendedProperty']", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Element[@Type = 'SqlSimpleColumn']/dac:AttachedAnnotation", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    清理

    删除产生了许多没有子元素的空元素。删除它们

    $dacpacXml.value.DocumentElement.SelectNodes("//dac:Entry[not(child::*)]", $ns) | % { $_.ParentNode.RemoveChild($_) } | Out-Null;
    

    保存更改

    $dacpacXML.Save("$expandedFolder/model.xml");
    

    重新压缩 DACPAC

    编辑后,我们需要将xml 文件转回dacpac。在此之前,需要重新计算和更新Origin.xml 中的校验和,这也是 dacpac 存档的一部分。校验和不匹配的 DACPAC 将无法工作。

    $newModelHash = (Get-FileHash -Path "$expandedFolder/model.xml").Hash;
    
    [xml] $originXML = Get-Content "$expandedFolder/Origin.xml";
    $originXML.DacOrigin.Checksums.Checksum.InnerText = $checksum;
    $originXML.Save("$expandedFolder/Origin.xml");
    

    现在我们准备好了,所以将其压缩回来(注意路径末尾的 /* 压缩所有文件,但不压缩包含文件夹)。复制,之后重命名。

    Compress-Archive -Path "$expandedFolder/*" -DestinationPath "$dacpac.zip" -CompressionLevel Optimal -Force;
    

    构建

    现在将您重新压缩的引用 (dacpacs) 放入适当的文件夹并重新运行构建过程。经测试,编辑后的 ​​dacpac 可以变小 10-50 倍,适用于 VS 或 MSBuild 中的参考解析。这个肮脏的技巧可以将构建时间减半。

    示例 SQLPROJ 构建 (cli msbuild) 统计信息,包含约 3K 进程、数百个表和 10-20 个 dacpac 依赖项:

    • 天真:~15min
    • p:CmdLineInMemoryStorage5-6分钟
    • 被黑的 dacpacs:2 分钟

    注意!获得的缩减 dacpac 可用于参考,但不适用于部署

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-29
      • 2018-05-06
      • 1970-01-01
      • 1970-01-01
      • 2014-02-03
      • 1970-01-01
      • 2020-07-02
      相关资源
      最近更新 更多