I'm currently inserting 1 million records into two tables at the same time in 90 to 100 seconds using a stored procedure. This in my scenario is not acceptable. I want to find a way to drop the time to less than 10 seconds.
I have tried inserting the records one after the order which was very slow - it took about an hour. Then I tried using table valued parameters to insert all the records once. This got the time down to 90 - 100 seconds.
This is the c# calling code:
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();
}
}
This is the GiftStreamingRecord class
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;
}
}
This is the stored procedure and the 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
All of these get me to insert 1 million in 90 to 100 seconds. I want to do it in less than 10 seconds.