如何在不耗尽内存的情况下部署包含 130,000 多个条目的脚本
Posted
技术标签:
【中文标题】如何在不耗尽内存的情况下部署包含 130,000 多个条目的脚本【英文标题】:How can I deploy a script with 130,000+ entries without running out of memory 【发布时间】:2015-11-09 07:40:54 【问题描述】:我在使用 SSDT 构建的 Visual Studio 2015 中有一个数据库项目。在这个项目中是一个 T4 模板,它生成对存储过程的 SQL 调用,以输入世界数据库的初始数据。
它包括国家、地区、时区、地区和城市。
当 T4 模板完成后,它会生成大约 130,000 个:
EXEC [geo].[addUpdateCountry] @countryCode = N'AD', @countryName = N'Andorra', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AE', @countryName = N'United Arab Emirates', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AF', @countryName = N'Afghanistan', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AG', @countryName = N'Antigua and Barbuda', @
然后,我将文件链接到 PostDeploy 脚本,以便在使用特定发布配置文件时运行这些脚本。
它太大了,发布过程内存不足,所以我想我必须以某种方式将它们分成几批,但我不确定在这种情况下我会如何做到这一点。
【问题讨论】:
【参考方案1】:当我遇到部署脚本和内存问题时,我已将我的脚本放入存储过程中,并从部署脚本中调用这些存储过程,您的 t4 模板是否会生成如下内容:
create proc deploy.country_data
as
EXEC [geo].[addUpdateCountry] @countryCode = N'AD', @countryName = N'Andorra', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AE', @countryName = N'United Arab Emirates', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AF', @countryName = N'Afghanistan', @initData = 1
etc...
然后从您的部署后脚本中调用它。
我通常将这些部署存储过程分离到一个单独的 ssdt 项目中,并使用 /p:IncludeCompositeObject=true 进行部署。
我还会提出一个关于 OO 异常的连接项。
您还可以通过使用 :r import 将不同的脚本导入主部署后脚本来分离部署脚本,但您需要知道脚本的位置。
【讨论】:
是的,我想我也会让它们成为一个单独的 SSDT,因为它会使主项目花费很长时间来构建 lol。【参考方案2】:我建议打开与您遇到的 OOM 异常相关的 Connect 问题。这可以在https://connect.microsoft.com/SQLServer/feedback/CreateFeedback.aspx 使用“开发人员工具(SSDT、BIDS 等)”类别完成。
要解决发布期间内存不足的问题,可以使用(64 位)命令行工具 SqlPackage.exe 来执行发布操作。 SqlPackage.exe 是一个命令行包装器,它围绕在 Visual Studio 和 SSMS 中执行发布操作的同一数据层应用程序框架代码。
SqlPackage.exe 可以从这里下载:http://www.microsoft.com/en-us/download/details.aspx?id=49500
可在此处获取文档:https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx
示例用法:
SqlPackage.exe /a:publish /sf:C:\temp\mydb.dacpac /tcs:"Data Source=myserver;Initial Catalog=mydb;Integrated Security=true"
【讨论】:
【参考方案3】:我在使用 T4 模板为我的数据库生成初始设置数据时遇到了一些问题。
1:没有 .sql 输出扩展名的 T4 模板在每次模板重新运行时将其构建操作重置为构建。这是一个问题,因为我想将脚本链接到 postDeploy 脚本并且不希望它构建,因为它会引发构建错误。
2:部署后脚本太大以至于部署它时内存不足,因为它还有用于调试的打印语句。
为了解决问题,我使用@Ed Alliot 的解决方案来制作我所有的初始化数据存储过程。但是,我创建了一个名为 setup 的新模式,并将所有设置存储过程放在该模式中,以便它们可以与数据库的其余部分分开保护。因为我没有生成存储过程,所以我可以让他们的构建操作保持“构建”,它将部署存储过程。
如果有人对这个设计过程感兴趣,我会发布一些屏幕和 t4 模板以供参考。
geoDataSql.ttinclude
<#@ template language="C#" hostspecific="true" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>
<#@ output extension=".sql" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#+
string countriesFile = "";
string zonesFile = "";
string timeZonesFile = "";
string regionsFile = "";
string citiesFile = "";
public void GenerateCountries()
List<country> countries = ParseCSV<country>(Host.ResolvePath("..\\T4\\geoSetup\\" + countriesFile));
WriteLine("");
WriteLine("-- Sql to add/update countries");
foreach(var country in countries)
if(string.IsNullOrEmpty(country.country_name) || string.IsNullOrEmpty(country.country_code))
WriteLine("--Skipped: country_code:" + country.country_code + "\ncountry_code was null or empty in the csv file.");
continue;
WriteLine(string.Format("EXEC [geo].[addUpdateCountry] @countryCode = N'0', @countryName = N'1', @initData = 1", country.country_code, country.country_name.Replace("'", "''")));
public void GenerateZones()
List<zone> zones = ParseCSV<zone>(Host.ResolvePath("..\\T4\\geoSetup\\" + zonesFile));
WriteLine("");
WriteLine("-- Sql to add/update zones");
foreach(var zone in zones)
WriteLine(string.Format("EXEC [geo].[addUpdateZone] @id=0, @countryCode = N'1', @zoneName = N'2', @initData = 1", zone.zone_id, zone.country_code, zone.zone_name.Replace("'", "''")));
public void GenerateTimeZones()
List<timezone> timeZones = ParseCSV<timezone>(Host.ResolvePath("..\\T4\\geoSetup\\" + timeZonesFile));
WriteLine("");
WriteLine("-- Sql to add/update zones");
foreach(var timeZone in timeZones)
if (string.IsNullOrEmpty(timeZone.time_start))
timeZone.time_start = "0";
if (string.IsNullOrEmpty(timeZone.gmt_offset))
timeZone.gmt_offset = "0";
WriteLine(string.Format("EXEC [geo].[addUpdateTimeZone] @zoneId=0, @zoneShortName = N'1', @timeStart = 2, @gmtOffset = 3, @dst = 4, @initData = 1", timeZone.zone_id, timeZone.abbreviation, timeZone.time_start, timeZone.gmt_offset, timeZone.dst));
public void GenerateRegions()
List<city> cities = ParseCSV<city>(Host.ResolvePath("..\\T4\\geoSetup\\" + citiesFile));
List<region> regionsOther = ParseCSV<region>(Host.ResolvePath("..\\T4\\geoSetup\\" + regionsFile));
Dictionary<string, region> regions = new Dictionary<string, region>();
foreach(var city in cities)
if (string.IsNullOrEmpty(city.city_name))
continue;
if (!string.IsNullOrEmpty(city.subdivision_1_iso_code))
string rKey = city.country_iso_code + "_" + city.subdivision_1_iso_code;
region r = new region() countryCode = city.country_iso_code, regionCode = city.subdivision_1_iso_code, regionName = city.subdivision_1_name ;
if (!regions.ContainsKey(rKey))
regions[rKey] = r;
if (!string.IsNullOrEmpty(city.subdivision_2_iso_code))
string rKey = city.country_iso_code + "_" + city.subdivision_2_iso_code;
region r = new region() countryCode = city.country_iso_code, regionCode = city.subdivision_2_iso_code, regionName = city.subdivision_2_name ;
if (!regions.ContainsKey(rKey))
regions[rKey] = r;
foreach (var region in regionsOther)
string rKey = region.countryCode + "_" + region.regionCode;
if (!regions.ContainsKey(rKey))
regions[rKey] = region;
WriteLine("");
WriteLine("-- Regions (Pulled from cities.csv, only regions with cities/towns etc are here.)");
foreach(var region in regions.Values)
WriteLine(string.Format("EXEC [geo].[addUpdateRegion] @countryCode = N'0', @regionCode = N'1', @regionName = N'2', @initData = 1", region.countryCode, region.regionCode, region.regionName.Replace("'", "''")));
public void GenerateCities()
List<city> cities = ParseCSV<city>(Host.ResolvePath("..\\T4\\geoSetup\\" + citiesFile));
WriteLine("");
WriteLine("-- Cities");
foreach (var city in cities)
if (string.IsNullOrEmpty(city.city_name))
continue;
if (string.IsNullOrEmpty(city.subdivision_1_iso_code) && string.IsNullOrEmpty(city.subdivision_2_iso_code))
WriteLine("--Skipped City: " + city.geoname_id.ToString() + " it doesn't have any region info!");
continue;
string sql = "EXEC [geo].[addUpdateCity] @countryCode = N'0', @cityName = N'1', @region1Code = 2, @region2Code = 3, @zoneName = N'4', @initData = 1";
WriteLine(string.Format(sql, city.country_iso_code, city.city_name.Replace("'", "''"), city.subdivision_1_iso_code == null ? "null" : "N'" + city.subdivision_1_iso_code + "'", city.subdivision_2_iso_code == null ? "null" : "N'" + city.subdivision_2_iso_code + "'", city.time_zone));
public List<T> ParseCSV<T>(string filePath) where T: class, new()
if (!System.IO.File.Exists(filePath))
return null;
string[] csvContents = File.ReadAllText(filePath).Split(new string[] "\n" , StringSplitOptions.RemoveEmptyEntries);
if (csvContents.Length <= 1)
return null;
Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);
string[] fieldNames = csvContents[0].Split(new string[] "," , StringSplitOptions.None);
if (fieldNames.Length <= 0)
return null;
List<T> ret = new List<T>();
Type objType = typeof(T);
for(int i = 1; i < csvContents.Length; ++i)
List<string> values = new List<string>();
foreach (Match match in csvSplit.Matches(csvContents[i]))
values.Add(match.Value.TrimStart(',').Trim('"').Trim());
if (values.Count != fieldNames.Length)
throw new Exception("Test");
T obj = new T();
for(int i2 = 0; i2 < fieldNames.Length; ++i2)
var field = fieldNames[i2];
var props = objType.GetProperties();
var property = objType.GetProperty(field.Trim(), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.SetProperty);
if (property == null)
throw new Exception("PROPERTY NULL");
if (property == null)
throw new Exception("Unable to parse field: " + field + " into Type: " + objType.FullName + "\n unable to find property on the Type matching the fields name.");
if (property.PropertyType != typeof(string))
throw new Exception("Unable to parse field: " + field + " into Property becuase the Property is not of Type string. Type:" + objType.FullName);
string v = values[i2] == null || values[i2] == string.Empty ? null : values[i2].Trim();
property.SetValue(obj, v);
ret.Add(obj);
return ret;
public class city
public string geoname_id get; set;
public string locale_code get; set;
public string continent_code get; set;
public string continent_name get; set;
public string country_iso_code get; set;
public string country_name get; set;
public string subdivision_1_iso_code get; set;
public string subdivision_1_name get; set;
public string subdivision_2_iso_code get; set;
public string subdivision_2_name get; set;
public string city_name get; set;
public string metro_code get; set;
public string time_zone get; set;
public class region
public string countryCode get; set;
public string regionCode get; set;
public string regionName get; set;
public class country
public string country_code get; set;
public string country_name get; set;
public class zone
public string zone_id get; set;
public string country_code get; set;
public string zone_name get; set;
public class timezone
public string zone_id get; set;
public string abbreviation get; set;
public string time_start get; set;
public string gmt_offset get; set;
public string dst get; set;
#>
setupCountryData.tt
<#
countriesFile = "countries.csv";
WriteLine("CREATE PROCEDURE [setup].[setupCountryData] AS");
GenerateCountries();
WriteLine("RETURN 0;");
#>
要创建其他 csv 驱动的存储过程,只需将名为 GenerateXYZ() 的方法添加到 geoDataSql.ttinclude。然后创建一个类似 setupCountryData.sql 的文件并设置文件名并调用您添加的相应生成方法。
缺点: 这使得构建需要永远......所以我可能会在一段时间后将此逻辑移到控制台应用程序......然后在部署/更改后运行它。
【讨论】:
感谢您的文章,看看不同的人用 ssdt 做了什么真的很有趣 是的,由于 T4 模板随机重新运行,我在尝试浏览解决方案资源管理器时遇到了 Visual Studio 内存不足的问题......所以我想我要做一个使用 peta poco 运行和部署所有设置数据并写入日志的控制台应用程序。以上是关于如何在不耗尽内存的情况下部署包含 130,000 多个条目的脚本的主要内容,如果未能解决你的问题,请参考以下文章
如何在不耗尽内存的情况下运行大型 Mahout 模糊 kmeans 聚类?
如何使用 Python Ray 在不耗尽内存的情况下并行处理大量数据?