是否可以为数十个 UPDATE 语句创建单个 SQL 查询?它应该提高性能吗?在我的情况下它应该是什么样子?
Is it possible to create single SQL query for dozens of UPDATE statements? Should it increase performace? How should it look like in my case?
我有问题。在下面的代码中有一个 foreach
循环,其中为 trucks
集合中的每个对象 truck
创建了查询字符串。循环迭代 100 次(例如,集合中的 100 个对象)。带循环的方法被调用了 2000 次。
我放弃了使用 ORM 以获得更好的性能。但是,不幸的是,我对代码执行速度还是有点失望。
请注意,对于每个迭代对象,都会创建 string query
,例如,看起来像这样:
UPDATE dbo.Trucks
SET OfficialNumber = '5124095'
, Status = 'Undefined'
, PerformancesUpdate = @PerformancesUpdate
, PictureLink = 'http://www.somewebsite.com/photos/middle////.jpg'
WHERE TruckId = 405664;
接下来,在解析 command.Parameters
之后,如果它们存在(在本例中为 @PerformancesUpdate
),则通过带有 int rows = command.ExecuteNonQuery(); //execute SQL
的库方法发送查询。以此类推,100次。请注意,查询字符串取决于对象属性,因此每种情况都会有所不同。
这是我的问题。我想知道,是否可以为循环中的所有 100 个对象创建一个查询并立即发送?如果是,它应该提高性能吗?如果是,示例查询应该是什么样子?
这里是创建循环的代码,在其中创建和执行 SQL 查询:
using (SqlConnection connection = GetConnection(_connectionString))
using (SqlCommand command = connection.CreateCommand())
{
connection.Open();
foreach (SomeObject truck in trucks)
{
Console.WriteLine("Updating " + counter++ + " of " + trucks.Count);
//clean up string for basic data
truck.Status = CleanUpStringCases(truck.Status);
truck.Destination = CleanUpStringCases(truck.Destination);
//clean up string for full AIS data
if (dataType == "full")
{
truck.TruckType = CleanUpStringCases(truck.TruckType);
}
//PARSE
SomeObject existing = new SomeObject();
//find existing truck to be updated
if (truck.OfficialNumber > 0) existing = _context.trucks.Where(v => v.OfficialNumber == truck.OfficialNumber).FirstOrDefault();
StringBuilder querySb = new StringBuilder();
if (existing != null)
{
//update for basic data
querySb.Append("UPDATE dbo." + _trucksTableName + " SET OfficialNumber = '" + truck.OfficialNumber + "'");
if (existing.MNCI == 0) if (truck.MNCI.HasValue) querySb.Append(" , MNCI = '" + truck.MNCI + "'");
if (truck.LatestActivity.HasValue) querySb.Append(" , LatestActivity = @LatestActivity");
if (truck.ETA.HasValue) querySb.Append(" , ETA = @ETA");
if (!string.IsNullOrEmpty(truck.Status)) querySb.Append(" , Status = '" + truck.Status + "'");
if (!string.IsNullOrEmpty(truck.Destination)) querySb.Append(" , Destination = '" + truck.Destination + "'");
if (!string.IsNullOrEmpty(truck.Area)) querySb.Append(" , Area = '" + truck.Area + "'");
if (truck.HeadingTo.HasValue) querySb.Append(" , HeadingTo = @HeadingTo");
if (truck.Lat.HasValue) querySb.Append(" , Lat = @Lat");
if (truck.Lon.HasValue) querySb.Append(" , Lon = @Lon");
if (truck.Speed.HasValue)
{
querySb.Append(" , Speed = @Speed");
if ((existing.SpeedMax < truck.Speed || existing.SpeedMax == null) && truck.Speed != 0) querySb.Append(" , SpeedMax = @Speed"); //update speed max
}
//string for full AIS data
if (dataType == "full")
{
if (truck.PerformancesUpdate.HasValue) querySb.Append(" , PerformancesUpdate = @PerformancesUpdate");
if (!string.IsNullOrEmpty(truck.TruckType)) querySb.Append(" , TruckType = '" + truck.TruckType + "'");
if (!string.IsNullOrEmpty(truck.PictureLink)) querySb.Append(" , PictureLink = '" + truck.PictureLink + "'");
if (truck.LOA.HasValue) querySb.Append(" , LOA = '" + truck.LOA + "'");
if (truck.Height.HasValue) querySb.Append(" , Height = '" + truck.Height + "'");
}
querySb.Append(" WHERE truckId = " + existing.truckId + "; ");
}
try
{
string query = querySb.ToString();
command.CommandText = query;
if (query.Contains("LatestActivity ="))
command.Parameters.AddWithValue("@LatestActivity", truck.LatestActivity);
if (query.Contains("ETA ="))
command.Parameters.AddWithValue("@ETA", truck.ETA);
if (query.Contains("PerformancesUpdate ="))
command.Parameters.AddWithValue("@PerformancesUpdate", truck.PerformancesUpdate);
if (query.Contains("HeadingTo ="))
command.Parameters.AddWithValue("@HeadingTo", truck.HeadingTo);
if (query.Contains("Lat ="))
command.Parameters.AddWithValue("@Lat", truck.Lat);
if (query.Contains("Lon ="))
command.Parameters.AddWithValue("@Lon", truck.Lon);
if (query.Contains("Speed ="))
command.Parameters.AddWithValue("@Speed", truck.Speed);
command.CommandTimeout = 30;
command.CommandType = CommandType.Text;
int rows = command.ExecuteNonQuery(); //execute SQL
command.Parameters.Clear();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
connection.Close();
}
解决方法:
Martin`s 方法被标记为解决方案。在回答有关提高性能的问题时,在本地数据库上测试他的方法后,我可以知道,数据库更新速度的增加与单个查询中更新行的数量成正比。例如,当更新 300 行时,我花了大约 30 秒,加上 API 请求。更新 1000 行时,大约需要 80 秒。而且,正如大家提到的,唯一的限制是 参数的数量 ,所以我只是将双打和 DateTime 转换为 SQL 格式,去掉参数,然后 瞧.
我建议使用某种批量操作,因为多个单独的数据库往返会产生额外的开销(正如您所注意到的)。
可以实现批量更新,即使用以下方法:
- 通过使用专用于高速批量操作的external library
- 通过制作sql 以数据数组为参数的存储过程。有关 table 值参数的更多信息,请参阅 https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine
- 使用单个数据库事务从临时 table 到实际数据 table 的 BULK INSERT operation to a temporary table and run the T-SQL MERGE 操作存储所有行。
是的,可以在一次操作中发送多个更新命令。
您需要将命令添加到字符串生成器并为 SQL 参数编号。
SqlCommand command = connection.CreateCommand()
var querySb = new Stringbuilder();
for(int i = 0; i < trucks.Count; i++)
{
[...]
if (truck.Speed.HasValue)
{
querySb.Append(" , Speed = @Speed" + i);
command.Parameters.AddWithValue("@Speed" + i, truck.Speed);
}
querySb.AppendLine();
}
command.CommandText = querySb.ToString();
command.ExecuteNonQuery();
您可能会创建较小的行批次(不会一次发送所有行),一个命令中的参数数量上限为 2,098。
我有问题。在下面的代码中有一个 foreach
循环,其中为 trucks
集合中的每个对象 truck
创建了查询字符串。循环迭代 100 次(例如,集合中的 100 个对象)。带循环的方法被调用了 2000 次。
我放弃了使用 ORM 以获得更好的性能。但是,不幸的是,我对代码执行速度还是有点失望。
请注意,对于每个迭代对象,都会创建 string query
,例如,看起来像这样:
UPDATE dbo.Trucks
SET OfficialNumber = '5124095'
, Status = 'Undefined'
, PerformancesUpdate = @PerformancesUpdate
, PictureLink = 'http://www.somewebsite.com/photos/middle////.jpg'
WHERE TruckId = 405664;
接下来,在解析 command.Parameters
之后,如果它们存在(在本例中为 @PerformancesUpdate
),则通过带有 int rows = command.ExecuteNonQuery(); //execute SQL
的库方法发送查询。以此类推,100次。请注意,查询字符串取决于对象属性,因此每种情况都会有所不同。
这是我的问题。我想知道,是否可以为循环中的所有 100 个对象创建一个查询并立即发送?如果是,它应该提高性能吗?如果是,示例查询应该是什么样子?
这里是创建循环的代码,在其中创建和执行 SQL 查询:
using (SqlConnection connection = GetConnection(_connectionString))
using (SqlCommand command = connection.CreateCommand())
{
connection.Open();
foreach (SomeObject truck in trucks)
{
Console.WriteLine("Updating " + counter++ + " of " + trucks.Count);
//clean up string for basic data
truck.Status = CleanUpStringCases(truck.Status);
truck.Destination = CleanUpStringCases(truck.Destination);
//clean up string for full AIS data
if (dataType == "full")
{
truck.TruckType = CleanUpStringCases(truck.TruckType);
}
//PARSE
SomeObject existing = new SomeObject();
//find existing truck to be updated
if (truck.OfficialNumber > 0) existing = _context.trucks.Where(v => v.OfficialNumber == truck.OfficialNumber).FirstOrDefault();
StringBuilder querySb = new StringBuilder();
if (existing != null)
{
//update for basic data
querySb.Append("UPDATE dbo." + _trucksTableName + " SET OfficialNumber = '" + truck.OfficialNumber + "'");
if (existing.MNCI == 0) if (truck.MNCI.HasValue) querySb.Append(" , MNCI = '" + truck.MNCI + "'");
if (truck.LatestActivity.HasValue) querySb.Append(" , LatestActivity = @LatestActivity");
if (truck.ETA.HasValue) querySb.Append(" , ETA = @ETA");
if (!string.IsNullOrEmpty(truck.Status)) querySb.Append(" , Status = '" + truck.Status + "'");
if (!string.IsNullOrEmpty(truck.Destination)) querySb.Append(" , Destination = '" + truck.Destination + "'");
if (!string.IsNullOrEmpty(truck.Area)) querySb.Append(" , Area = '" + truck.Area + "'");
if (truck.HeadingTo.HasValue) querySb.Append(" , HeadingTo = @HeadingTo");
if (truck.Lat.HasValue) querySb.Append(" , Lat = @Lat");
if (truck.Lon.HasValue) querySb.Append(" , Lon = @Lon");
if (truck.Speed.HasValue)
{
querySb.Append(" , Speed = @Speed");
if ((existing.SpeedMax < truck.Speed || existing.SpeedMax == null) && truck.Speed != 0) querySb.Append(" , SpeedMax = @Speed"); //update speed max
}
//string for full AIS data
if (dataType == "full")
{
if (truck.PerformancesUpdate.HasValue) querySb.Append(" , PerformancesUpdate = @PerformancesUpdate");
if (!string.IsNullOrEmpty(truck.TruckType)) querySb.Append(" , TruckType = '" + truck.TruckType + "'");
if (!string.IsNullOrEmpty(truck.PictureLink)) querySb.Append(" , PictureLink = '" + truck.PictureLink + "'");
if (truck.LOA.HasValue) querySb.Append(" , LOA = '" + truck.LOA + "'");
if (truck.Height.HasValue) querySb.Append(" , Height = '" + truck.Height + "'");
}
querySb.Append(" WHERE truckId = " + existing.truckId + "; ");
}
try
{
string query = querySb.ToString();
command.CommandText = query;
if (query.Contains("LatestActivity ="))
command.Parameters.AddWithValue("@LatestActivity", truck.LatestActivity);
if (query.Contains("ETA ="))
command.Parameters.AddWithValue("@ETA", truck.ETA);
if (query.Contains("PerformancesUpdate ="))
command.Parameters.AddWithValue("@PerformancesUpdate", truck.PerformancesUpdate);
if (query.Contains("HeadingTo ="))
command.Parameters.AddWithValue("@HeadingTo", truck.HeadingTo);
if (query.Contains("Lat ="))
command.Parameters.AddWithValue("@Lat", truck.Lat);
if (query.Contains("Lon ="))
command.Parameters.AddWithValue("@Lon", truck.Lon);
if (query.Contains("Speed ="))
command.Parameters.AddWithValue("@Speed", truck.Speed);
command.CommandTimeout = 30;
command.CommandType = CommandType.Text;
int rows = command.ExecuteNonQuery(); //execute SQL
command.Parameters.Clear();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
connection.Close();
}
解决方法: Martin`s 方法被标记为解决方案。在回答有关提高性能的问题时,在本地数据库上测试他的方法后,我可以知道,数据库更新速度的增加与单个查询中更新行的数量成正比。例如,当更新 300 行时,我花了大约 30 秒,加上 API 请求。更新 1000 行时,大约需要 80 秒。而且,正如大家提到的,唯一的限制是 参数的数量 ,所以我只是将双打和 DateTime 转换为 SQL 格式,去掉参数,然后 瞧.
我建议使用某种批量操作,因为多个单独的数据库往返会产生额外的开销(正如您所注意到的)。
可以实现批量更新,即使用以下方法:
- 通过使用专用于高速批量操作的external library
- 通过制作sql 以数据数组为参数的存储过程。有关 table 值参数的更多信息,请参阅 https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine
- 使用单个数据库事务从临时 table 到实际数据 table 的 BULK INSERT operation to a temporary table and run the T-SQL MERGE 操作存储所有行。
是的,可以在一次操作中发送多个更新命令。
您需要将命令添加到字符串生成器并为 SQL 参数编号。
SqlCommand command = connection.CreateCommand()
var querySb = new Stringbuilder();
for(int i = 0; i < trucks.Count; i++)
{
[...]
if (truck.Speed.HasValue)
{
querySb.Append(" , Speed = @Speed" + i);
command.Parameters.AddWithValue("@Speed" + i, truck.Speed);
}
querySb.AppendLine();
}
command.CommandText = querySb.ToString();
command.ExecuteNonQuery();
您可能会创建较小的行批次(不会一次发送所有行),一个命令中的参数数量上限为 2,098。