我目前正在使用存储过程在90到100秒内将100万条记录同时插入两个表中。在我的情况下,这是不可接受的。我想找到一种将时间减少到10秒以内的方法。
我尝试在订单很慢之后插入一个记录-大约花了一个小时。然后,我尝试使用表值参数插入所有记录一次。时间缩短到90-100秒。
这是c#调用代码:
public Task<int> CreateGiftVoucher(IEnumerable<Gift> vouchersList)
{
GiftStreamingSqlRecord record = new GiftStreamingSqlRecord(vouchersList);
foreach (var t in vouchersList)
{
Console.WriteLine($"<<<<<gfts>>> {t}");
}
try
{
var connection = Connection;
if (connection.State == ConnectionState.Closed) connection.Open();
string storedProcedure = "dbo.usp_CreateGiftVoucher";
var command = new SqlCommand(storedProcedure, connection as SqlConnection);
command.CommandType = CommandType.StoredProcedure;
var param = new SqlParameter();
param.ParameterName = "@tblGift";
param.TypeName = "dbo.GiftVoucherType";
param.SqlDbType = SqlDbType.Structured;
param.Value = record;
command.Parameters.Add(param);
command.CommandTimeout = 60;
return command.ExecuteNonQueryAsync();
}
catch (System.Exception)
{
throw;
}
finally
{
Connection.Close();
}
}
这是GiftStreamingRecord类
public GiftStreamingSqlRecord(IEnumerable<Gift> gifts) => this._gifts = gifts;
public IEnumerator<SqlDataRecord> GetEnumerator()
{
SqlMetaData[] columnStructure = new SqlMetaData[11];
columnStructure[0] = new SqlMetaData("VoucherId",SqlDbType.BigInt,
useServerDefault: false,
isUniqueKey: true,
columnSortOrder:SortOrder.Ascending, sortOrdinal: 0);
columnStructure[1] = new SqlMetaData("Code", SqlDbType.NVarChar, maxLength: 100);
columnStructure[2] = new SqlMetaData("VoucherType", SqlDbType.NVarChar, maxLength: 50);
columnStructure[3] = new SqlMetaData("CreationDate", SqlDbType.DateTime);
columnStructure[4] = new SqlMetaData("ExpiryDate", SqlDbType.DateTime);
columnStructure[5] = new SqlMetaData("VoucherStatus", SqlDbType.NVarChar, maxLength: 10);
columnStructure[6] = new SqlMetaData("MerchantId", SqlDbType.NVarChar, maxLength: 100);
columnStructure[7] = new SqlMetaData("Metadata", SqlDbType.NVarChar, maxLength: 100);
columnStructure[8] = new SqlMetaData("Description", SqlDbType.NVarChar, maxLength: 100);
columnStructure[9] = new SqlMetaData("GiftAmount", SqlDbType.BigInt);
columnStructure[10] = new SqlMetaData("GiftBalance", SqlDbType.BigInt);
var columnId = 1L;
foreach (var gift in _gifts)
{
var record = new SqlDataRecord(columnStructure);
record.SetInt64(0, columnId++);
record.SetString(1, gift.Code);
record.SetString(2, gift.VoucherType);
record.SetDateTime(3, gift.CreationDate);
record.SetDateTime(4, gift.ExpiryDate);
record.SetString(5, gift.VoucherStatus);
record.SetString(6, gift.MerchantId);
record.SetString(7, gift.Metadata);
record.SetString(8, gift.Description);
record.SetInt64(9, gift.GiftAmount);
record.SetInt64(10, gift.GiftBalance);
yield return record;
}
}
这是存储过程和tvp:
CREATE TYPE [dbo].GiftVoucherType AS TABLE (
[VoucherId] [bigint] PRIMARY KEY,
[Code] [nvarchar](100) NOT NULL,
[VoucherType] [nvarchar](50) NOT NULL,
[CreationDate] [datetime] NOT NULL,
[ExpiryDate] [datetime] NOT NULL,
[VoucherStatus] [nvarchar](10) NOT NULL,
[MerchantId] [nvarchar](100) NOT NULL,
[Metadata] [nvarchar](100) NULL,
[Description] [nvarchar](100) NULL,
[GiftAmount] [bigint] NOT NULL,
[GiftBalance] [bigint] NOT NULL
)
GO
CREATE PROCEDURE [dbo].[usp_CreateGiftVoucher]
@tblGift [dbo].GiftVoucherType READONLY
AS
DECLARE @idmap TABLE (TempId BIGINT NOT NULL PRIMARY KEY,
VId BIGINT UNIQUE NOT NULL)
BEGIN TRY
BEGIN TRANSACTION CreateGiftVoucher
MERGE Voucher V
USING (SELECT [VoucherId], [Code], [VoucherType], [MerchantId], [ExpiryDate],
[Metadata], [Description] FROM @tblGift) TB ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT ([Code], [VoucherType], [MerchantId], [ExpiryDate], [Metadata], [Description])
VALUES(TB.Code, TB.VoucherType, TB.MerchantId, TB.ExpiryDate, TB.Metadata, TB.[Description])
OUTPUT TB.VoucherId, inserted.VoucherId INTO @idmap(TempId, VId);
-- Insert rows into table 'GiftVoucher'
INSERT GiftVoucher
(
GiftAmount, GiftBalance, VoucherId
)
SELECT TB.GiftAmount, TB.GiftBalance, i.VId
FROM @tblGift TB
JOIN @idmap i ON i.TempId = TB.VoucherId
COMMIT TRANSACTION CreateGiftVoucher
END TRY
BEGIN CATCH
ROLLBACK
END CATCH
GO
所有这些使我能够在90到100秒内插入100万。 我想在10秒内完成。
答案 0 :(得分:6)
插入大量行的最快方法是使用批量插入(SqlBulkCopy
或其他API)。我可以看到您正在使用MERGE
。批量复制无法使用该功能,因此该设计将强制使用表值参数,就像您现在正在使用它们一样。 TVP在使用更多CPU方面要慢一些。您也可以尝试批量插入临时表,然后使用MERGE
。据我了解,TVP实际上是一个临时表。没有真正的流媒体正在进行。服务器将您在C#代码中流式传输到其中的所有数据简单地插入到自动管理的表中。
您执行的TVP流(SqlMetaData
)是正确的。根据我的经验,这是传输TVP数据的最快方法。
您将需要并行化。根据经验,对于相当简单的行,在最佳条件下很难超过每秒10万行。此时,CPU在一个内核上已饱和。您可以在记录的某些条件下在多个内核上并行插入。索引结构有一些要求。您可能还会遇到锁定问题。解决这些问题的肯定方法是插入独立的表或分区中。但这当然会迫使您更改针对这些表运行的其他查询。
如果在插入时必须执行复杂的逻辑,则仍可以插入到新表中,然后在查询时执行逻辑。这比较麻烦且容易出错,但可以满足您的延迟要求。
我希望这些想法能帮助您走上正确的道路。随时发表评论。