2010-07-19 8 views
8

Estoy trabajando en un componente .NET que obtiene un conjunto de datos de la base de datos, realiza cierta lógica comercial en ese conjunto de datos y luego actualiza registros individuales en la base de datos a través de un procedimiento almacenado similar al spUpdateOrderDetailDiscountedItem.¿Cuál es una buena alternativa para disparar un procedimiento almacenado 368 veces para actualizar la base de datos?

Para pequeños conjuntos de datos, esto no es un problema, pero cuando tenía un gran conjunto de datos que requerían una iteración de 368 llamadas de proceso almacenadas para actualizar los registros en la base de datos, me di cuenta de que tenía un problema . Un desarrollador senior miró mi código de proceso almacenado y dijo que se veía bien, pero ahora me gustaría explorar un mejor método para enviar datos "por lotes" a la base de datos.

¿Qué opciones tengo para actualizar la base de datos por lotes? ¿Es esto posible con procs almacenados? ¿Qué otras opciones tengo?

No tendré la opción de instalar un ORM en toda regla, pero cualquier consejo es apreciado.


Antecedentes Información adicional:

Nuestro modelo de acceso a datos actual fue construido hace 5 años y todas las llamadas a la base de datos recibe actualmente ejecuta a través de las funciones modulares/estáticas con nombres como ExecQuery y GetDataTable. No estoy seguro de que esté requiriera para permanecer dentro de ese modelo, pero tendría que proporcionar una muy buena justificación para salir de nuestro DAL actual para llegar al DB.

También vale la pena señalar, soy bastante nuevo en lo que respecta a las operaciones CRUD y la base de datos. Prefiero jugar/trabajar en el lado .NET del código, pero los datos tienen que almacenarse en algún lugar, ¿verdad?


contenidos procedimiento almacenado:

ALTER PROCEDURE [dbo].[spUpdateOrderDetailDiscountedItem] 
    -- Add the parameters for the stored procedure here 
    @OrderDetailID decimal = 0, 
    @Discount money = 0, 
    @ExtPrice money = 0, 
    @LineDiscountTypeID int = 0, 
    @OrdersID decimal = 0, 
    @QuantityDiscounted money = 0, 
    @UpdateOrderHeader int = 0, 
    @PromoCode varchar(6) = '', 
    @TotalDiscount money = 0 

AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    -- Insert statements for procedure here 
    Update OrderDetail 
    Set Discount = @Discount, ExtPrice = @ExtPrice, LineDiscountTypeID = @LineDiscountTypeID, LineDiscountPercent = @QuantityDiscounted 
    From OrderDetail with (nolock) 
    Where OrderDetailID = @OrderDetailID 

    if @UpdateOrderHeader = -1 
     Begin 
     --This code should get code the last time this query is executed, but only then. 
     exec spUpdateOrdersHeaderForSkuGroupSourceCode @OrdersID, 7, 0, @PromoCode, @TotalDiscount 
     End 
+8

368 operaciones no es mucho. – Fosco

+0

Necesita ver el contenido del procedimiento almacenado, incluso si se ha abstraído ... –

+2

Casi todos los ORM aún realizarán 368 llamadas a la base de datos. Simplemente las procesan por lotes. – NotMe

Respuesta

9

Una manera fácil y alternativa que he visto en uso es construir una declaración SQL que consta de sql_execs llamando al sproc con los parámetros en la cadena. No estoy seguro de si esto es aconsejable o no, pero desde la perspectiva de .NET, solo está llenando un SqlCommand y llamando a ExecuteNonQuery una vez ...

Tenga en cuenta que si elige esto, por favor, utilice el StringBuilder! :-)

Actualización: me gusta mucho más animado de Chris respuesta, no sabía acerca de los parámetros con valores de tabla hasta ahora ... por desgracia el PO está utilizando 2005.

+0

Esta es probablemente la solución menos invasiva. +1 – Chris

+0

@Chris Vi que solía agrupar múltiples comandos de bases de datos de forma relativamente limpia desde el código; aún así obtiene los beneficios de los sprocs, pero necesita una pequeña lógica adicional para codificar los parámetros en la cadena de una manera válida. No es mi ruta favorita, pero comprensible. –

+1

¿No cree que debe preocuparse por las consultas parametrizadas si todo lo que está haciendo es llamar a los procs, ya que estos tienen su propio plan de consulta de todos modos? –

3

puede enviar el conjunto completo de datos XML como entrada para el procedimiento almacenado. Luego puede realizar operaciones de configuración para modificar la base de datos. El conjunto basado superará a los RBAR en el rendimiento casi cada vez.

+0

Gracias por la respuesta. ¿Qué es un RBAR? –

+0

@Ben: Fila por Agonizing Row. –

+0

Ha. Sí, perfectamente resume la situación. –

0

se puede crear comunicado por lotes con 368 llama a su proc, entonces al menos no tendrá 368 viajes redondos. es decir, pseudo código

var lotsOfCommands = "spUpdateOrderDetailDiscountedItem 1; spUpdateOrderDetailDiscountedItem 2;spUpdateOrderDetailDiscountedItem ... 368' 

var new sqlcommand(lotsOfCommands) 
command.CommandType = CommandType.Text; 

//execute command 
+0

Esto no resuelve nada, y depende de su código para escapar de los valores, etc. correctamente, lo que es malo. Suponiendo una conexión de base de datos persistente, el tiempo que lleva enviar el comando debe ser mínimo. Mientras técnicamente estás combinando todas las actualizaciones en un lote poniéndolas en una sola instrucción SQL, no estás resolviendo el problema de llamar a un sproc 368 veces (¡lo que aún hace el SQL concatenado!). Además, esto elimina su capacidad de rastrear errores por actualización. –

+0

Bueno, lo siento, pero ¿de verdad crees que la reducción de 368 viajes de ida y vuelta a uno no aumentará el rendimiento? También es el cambio menos invasivo. Aprecio que necesitarás escapar de los valores. Sí, todo el lote generará un error en lugar de una ejecución de proceso, dependiendo de la semántica de lo que se está haciendo, es posible que desee deshacer todo el lote, lo que aún es posible con el procesamiento por lotes. No he usado SQLCLR, por lo que no puedo comentar sobre él, por lo que he leído al respecto, este escenario podría ser un uso razonable para él. –

16

Si está utilizando SQL 2008, a continuación, se puede utilizar un table-valued parameter para empujar todas las actualizaciones en una llamada s'proc.

actualización Por cierto, estamos usando esto en combinación con la declaración merge. De esta forma, el servidor sql se ocupa de averiguar si estamos insertando nuevos registros o actualizando los existentes. Este mecanismo se usa en varias ubicaciones principales en nuestra aplicación web y maneja cientos de cambios a la vez. Durante la carga normal veremos que este proceso se llama alrededor de 50 veces por segundo y es MUCHO más rápido que cualquier otra forma que hayamos encontrado ... y ciertamente MUCHO más barato que comprar servidores DB más grandes.

+1

Enlace de soporte: http://msdn.microsoft.com/en-us/library/bb675163.aspx –

+0

No había oído hablar de ellos anteriormente. Gracias por la sugerencia. ¿Podría mostrar o vincular un ejemplo de cómo puede usar eso en el código tanto en el cliente como en la definición del proceso almacenado? –

+0

@Adam me ganaste! ¡Gracias! –

0

Tuve problemas al intentar hacer lo mismo (a través de inserciones, actualizaciones, lo que sea). Mientras usaba un OleDbCommand con parámetros, tomó un montón de tiempo recrear constantemente el objeto y los parámetros cada vez que lo llamé. Por lo tanto, hice una propiedad en mi objeto para manejar dicha llamada y también agregué los "parámetros" apropiados a la función. Luego, cuando necesitaba llamarlo/ejecutarlo, recorría cada parámetro en el objeto, lo configuraba en lo que necesitaba y lo ejecutaba. Esto creó mejora significativa del rendimiento ... Tal pseudo-código de mi operación:

protected OleDbCommand oSQLInsert = new OleDbCommand(); 
// the "?" are place-holders for parameters... can be named parameters, 
// just for visual purposes 
oSQLInsert.CommandText = "insert into MyTable (fld1, fld2, fld3) values (?, ?, ?)"; 
// Now, add the parameters 
OleDbParameter NewParm = new OleDbParameter("parmFld1", 0); 
oSQLInsert.Parameters.Add(NewParm); 

NewParm = new OleDbParameter("parmFld2", "something"); 
oSQLInsert.Parameters.Add(NewParm); 

NewParm = new OleDbParameter("parmFld3", 0); 
oSQLInsert.Parameters.Add(NewParm); 

Ahora, el comando SQL, y colocar los titulares de la llamada son todo listo para ir ... Entonces, cuando estoy listo para llamarlo, haria algo como ...

oSQLInsert.Parameters[0].Value = 123; 
oSQLInsert.Parameters[1].Value = "New Value"; 
oSQLInsert.Parameters[2].Value = 3; 

Luego, solo ejecútelo. La repetición de cientos de llamadas podría ser eliminada por el tiempo al crear sus comandos una y otra vez ...

buena suerte.

+0

Me gustaría ejecutar esto a través de un perfil para asegurarse de que el constructor y la colección son realmente lo que está comiendo el tiempo. –

0

¿Se trata de una acción de una sola vez (como "solo importa esos 368 nuevos clientes una vez") o regularmente tiene que hacer 368 llamadas sproc?

Si se trata de una acción de una sola vez, simplemente continúe con las 368 llamadas.
(si el sproc hace mucho más que solo actualizaciones y es probable que reduzca el rendimiento, ejecútelo por la noche o por la noche o cuando no haya nadie trabajando).

IMO, la optimización prematura de las llamadas a la base de datos para acciones únicas no vale la pena el tiempo que pasa con ella.

1

Si está utilizando una versión de SQL Server anterior hasta 2008, puede mover su código completamente en el procedimiento almacenado.

Hay cosas buenas y "malas" sobre esto.
Buena

  • No hay necesidad de tirar de los datos a través de un cable de red.
  • más rápido si su lógica se establece en función
  • escala hasta

Malo

  • Si tiene reglas contra toda lógica en la base de datos, esto rompería el diseño.
  • Si la lógica no se puede establecer basa entonces usted puede ser que terminar con un conjunto diferente de problemas de rendimiento
  • Si tiene dependencias externas, esto puede aumentar la dificultad.

Sin detalles sobre exactamente qué operaciones está realizando en los datos es difícil dar una recomendación sólida.

ACTUALIZACIÓN
Ben preguntó lo que quería decir en una de mis comentarios sobre el CLR y SQL Server. Lee Using CLR Integration in SQL Server 2005. La idea básica es que puede escribir código .Net para hacer su manipulación de datos y hacer que ese código viva dentro del servidor SQL. Esto le ahorra tener que leer todos los datos en la red y enviar actualizaciones de esa manera.

El código es invocable por sus procesos existentes y le proporciona toda la potencia de .net para que no tenga que hacer cosas como cursores. El sql permanecerá establecido mientras que el código .net puede realizar operaciones en registros individuales.

Por cierto, esto es cómo se implementaron cosas como heirarchyid en SQL 2008.

El único inconveniente es que algunos de DBA no les gusta introducir el código del desarrollador como éste en el servidor de base de datos. Entonces, dependiendo de su entorno, esta puede no ser una opción. Sin embargo, si lo es, entonces es una manera muy poderosa de resolver su problema mientras deja los datos y el procesamiento dentro de su servidor de base de datos.

+0

@ Chris, aparece un buen punto sobre posiblemente mover el código completamente al proceso almacenado. El problema es que la lógica de "autodescuento" es un poco complicada y casi con seguridad requeriría usar cursores en el proceso almacenado. Eso me parece difícil de mantener desde la perspectiva del código. –

+0

@Ben McCormack: en ese caso, todavía tiene una opción más disponible bajo ciertas versiones de SQL Server: el CLR. Pero ahora estamos entrando en una tierra de la que la mayoría de la gente se mantiene alejada ... con buenas razones. ;) – NotMe

+0

@Chris, ¿qué piensas cuando dices que CLR es una opción cuando se trabaja con SQL Server? Obviamente, el CLR * es * el motor que ejecuta el código administrado .NET, pero ¿qué tiene eso que ver con SQL Server? –

0

Los parámetros con valores de tabla serían los mejores, pero como está en SQL 05, puede usar la clase SqlBulkCopy para insertar lotes de registros. En mi experiencia, esto es muy rápido.

Cuestiones relacionadas