是否可以为数十个 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 格式,去掉参数,然后 .

我建议使用某种批量操作,因为多个单独的数据库往返会产生额外的开销(正如您所注意到的)。

可以实现批量更新,即使用以下方法:

是的,可以在一次操作中发送多个更新命令。

您需要将命令添加到字符串生成器并为 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。