performance - keepidentity - Las inserciones a granel tardan más de lo esperado usando Dapper
sqlbulkcopy example (5)
Después de leer este artículo , decidí echar un vistazo más de cerca a la forma en que estaba usando Dapper.
Corrí este código en una base de datos vacía
var members = new List<Member>();
for (int i = 0; i < 50000; i++)
{
members.Add(new Member()
{
Username = i.toString(),
IsActive = true
});
}
using (var scope = new TransactionScope())
{
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
scope.Complete();
}
tardó unos 20 segundos. Eso es 2500 inserciones / segundo. No está mal, pero tampoco es bueno considerando que el blog estaba logrando 45k inserciones / segundo. ¿Hay una manera más eficiente de hacer esto en Dapper?
Además, como nota al margen, ¡ejecutar este código a través del depurador de Visual Studio tomó más de 3 minutos! Pensé que el depurador lo retrasaría un poco, pero realmente me sorprendió ver eso.
ACTUALIZAR
Así que esto
using (var scope = new TransactionScope())
{
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
scope.Complete();
}
y esto
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
ambos tardaron 20 segundos.
¡Pero esto tomó 4 segundos!
SqlTransaction trans = connection.BeginTransaction();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members, transaction: trans);
trans.Commit();
El uso del método Execute
con una sola instrucción de inserción nunca hará una inserción masiva ni será eficiente. Incluso la respuesta aceptada con una Transaction
no hace un Bulk Insert
.
Si desea realizar una Bulk Insert
, use SqlBulkCopy
https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy
No encontrarás nada más rápido que esto.
Dapper Plus
Descargo de responsabilidad : soy el dueño del proyecto Dapper Plus
Este proyecto no es gratuito pero ofrece todas las operaciones a granel:
- BulkInsert
- Actualización masiva
- BulkDelete
- BulkMerge
(Usar bajo el capó SqlBulkCopy
)
Y algunas opciones más, como la salida de valores de identidad:
// CONFIGURE & MAP entity
DapperPlusManager.Entity<Order>()
.Table("Orders")
.Identity(x => x.ID);
// CHAIN & SAVE entity
connection.BulkInsert(orders)
.AlsoInsert(order => order.Items);
.Include(x => x.ThenMerge(order => order.Invoice)
.AlsoMerge(invoice => invoice.Items))
.AlsoMerge(x => x.ShippingAddress);
Nuestra biblioteca soporta múltiples proveedores:
- servidor SQL
- Compacto de SQL
- Oráculo
- MySql
- PostgreSQL
- SQLite
- Firebird
Encontré todos estos ejemplos incompletos.
Aquí hay un código que cierra correctamente la conexión después de su uso, y también usa correctamente el transaccional para mejorar el rendimiento de ejecución, basado en las respuestas más recientes y mejores de este hilo.
using (var scope = new TransactionScope())
{
Connection.Open();
Connection.Execute(sqlQuery, parameters);
scope.Complete();
}
Lo mejor que pude lograr fue 50k registros en 4 segundos usando este enfoque
SqlTransaction trans = connection.BeginTransaction();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members, transaction: trans);
trans.Commit();
Me encontré con esto recientemente y me di cuenta de que el TransactionScope se crea después de que se abre la conexión (asumo esto ya que Dappers Execute no abre la conexión, a diferencia de Query). De acuerdo con la respuesta P4 aquí: https://.com/a/2886326/455904 que no resultará en que la conexión sea manejada por el TransactionScope. Mi compañero de trabajo hizo algunas pruebas rápidas, y al abrir la conexión fuera del TransactionScope se redujo drásticamente el rendimiento.
Así que cambiar a lo siguiente debería funcionar:
// Assuming the connection isn''t already open
using (var scope = new TransactionScope())
{
connection.Open();
connection.Execute(@"
insert Member(Username, IsActive)
values(@Username, @IsActive)", members);
scope.Complete();
}
la variante más rápida para mí:
var dynamicParameters = new DynamicParameters();
var selects = new List<string>();
for (var i = 0; i < members.Length; i++)
{
var member = members[i];
var pUsername = $"u{i}";
var pIsActive = $"a{i}";
dynamicParameters.Add(pUsername, member.Username);
dynamicParameters.Add(pIsActive, member.IsActive);
selects.Add("select @{pUsername},@{pIsActive}");
}
con.Execute($"insert into Member(Username, IsActive){string.Join(" union all ", selects)}", dynamicParameters);
que generan sql como:
INSERT TABLENAME (Column1,Column2,...)
SELECT @u0,@a0...
UNION ALL
SELECT @u1,@a1...
UNION ALL
SELECT @u2,@a2...
esta consulta funciona más rápido porque sql agrega un conjunto de filas en lugar de agregar una fila a la vez. El cuello de botella no es escribir los datos, está escribiendo lo que está haciendo en el registro.
Además, observe las reglas de las transacciones mínimamente registradas.