Buscar este blog

martes, 13 de julio de 2010

Optimización en la actualización de lote de registros en una tabla en la base de datos

Me han encargado el desarrollo de un programa que mezcle ciertos códigos que se encuentran en dos tablas en la base de datos con unos código fijos que se generan desde un arreglo en el código fuente. El numero de código en total son 5’000.000 de los cuales 700.000 salen de la base de datos. El núcleo de la aplicación radica en la aleatoriedad que deben tener estos 700.000 en los 5 millones evitándose periodicidades o agrupamientos.

Se crearon los métodos que se encargan de la aleatoriedad y su funcionamiento fue adecuado, pero todo se complico cuando en la primera simulación el cálculo estimado de tiempo que tomaría ejecutar el programa para todos los registros arrojo un valor cercano a las 30 horas.

Viendo esto llegamos a la conclusión era que las operaciones que se realizaban con la base de datos hacían que el proceso tomara este tiempo.

Realmente las operaciones eran sencillas, un SELECT que retornaba un solo registro de dos columnas y  si la operación era exitosa un UPDATE que me actualizaba este registró para cambiar su estado.

Pero estas dos simples operaciones más las comprobaciones internas del SqlConnection hacían que la ejecución total tomara tanto tiempo, fue entonces que decidí optimizar esto y el resultado fue que una reducción de 30 horas a aproximadamente 10 minutos, una reducción muy notoria.

Mi primer error fue utilizar de forma inadecuada las clases que provee el ADO.net y mi segundo error fue realizar constantes consultas la bd en vez de hacerlo sobre datos de acceso más rápido, por ejemplo en memoria.

Finalmente la solución fue muy sencilla, trabajar sobre una tabla replica de los registros en memoria realizar las operaciones de consulta y actualización sobre estos y luego actualizar la base de datos físicamente.

En el siguiente código traemos los primeros 1000 registros que deseamos utilizar, en su caso pueden traer los registros que desean con la consulta que ustedes utilicen.

Consulta y llenado
Luego con los métodos .FillSchema y .Fill del DataAdapter realizamos respectivamente un mapeo en memoria de la estructura de la tabla en un DataSet y luego procedemos a llenarlo con los datos que retorna la consulta SELECT, de esta forma tenemos en memoria una copia exacta de la tabla tanto de su estructura como de sus datos – los datos retornados en la consulta-.
string sql = @"SELECT TOP 1000 [id], [estado] FROM [tabla] WHERE [estado] <> 1 ORDER BY code ASC";

            daContenido = new SqlDataAdapter(sql, conn);
            dsContenido = new DataSet("Tabla");

            try
            {

                daContenido.FillSchema(dsContenido, SchemaType.Source, "tabla");
                daContenido.Fill(dsContenido, "tabla");

                tblContenido = dsContenido.Tables["tabla"];
            }
            catch (SqlException ex)
            {
                Console.WriteLine(ex.Message);
            }

Teniendo ya una copia de la tabla a consultar y modificar en memoria aseguramos no tenes que realizar consultas directas sobre la base de datos mejorando los tiempos de ejecucion.

Importante: lo negativo de este enfoque radica en la frecuencia de actualizacion sobre la tabla fisica por parte de otras aplicaciones y usuarios, ya que en un entorno donde la tabla fisica es actualizada frecuentemente en lapsos cortos de tiempo facilmente se puede presentar una falla en la integridad de los datos porque se estaria trabajando con una copia en memoria de la tabla que posiblemente haya sido modificada.

Sub-Selección de registros
Hasta este punto hemos avanzado hasta el punto de tener los datos y la estructura de la tabla en memoria, pero debemos poder consultarla como si fuera la tabla física en la base de datos, para esto en el siguiente segmento de código brevemente vemos una consulta básica.
Fácilmente se pueden construir consultas que devuelvan varios registros con varias columnas.
string codigo = "";

DataRow current;

/* Esto equivale a un SELECT de la tabla con un WHERE donde estado <> 1 y ordenado por codigo de forma ascendente */
DataRow[] filas = tblContenido.Select("estado <> 1", "code ASC");
           
if (filas.GetLength(0) > 0)
{
/* En este caso solo requiero el primer registro que cumpla las condiciones sin importar cual sea */
current = filas[0];

/* Me guarda el valor de la llave primaria de este registro en un string */
codigo = current[0].ToString();



Modificación de un registro
En cuanto a la modificacion de un registro sobre esta tabla en memoria es un proceso muy simple y puede realizarce para todos los registros en la tabla disponible en memoria.

DataRow current;
try
{
/* Este metodo retorna un Datarow donde la llave primaria concuerde con el parametro pasado */
current = tblContenido.Rows.Find(code);

/* Se inicia el proceso de modificado*/
current.BeginEdit();
               
/* Se modifican las columnas deseadas */
current[1] = true;
               
/* Se finaliza el proceso de modificacion del registro */
current.EndEdit();
}
catch
{
Console.WriteLine("Excepcion");
}

Actualización de tabla original
Ya hemos realizado la modificación de los registros necesarios durante la ejecución del programa, a este punto lo que falta es actualizar estos cambios en la base de datos físicamente, actualizar la tabla. Para esto recurriremos al método .Update del DataAdapter.

try
{
/* Con el objeto SqlCommandBuilder nos encargamso de crear las plantillas de cadenas utilizadas para realizar los UPDATES sobre la tabla real en la base de datos */
SqlCommandBuilder objCommandBuilder = new SqlCommandBuilder(daContenido);

/* Actualizamos la tabla con el contenido actualizado del DataSet */
daContenido.Update(dsContenido, "tabla");
}
catch (SqlException ex)
{
      Console.WriteLine(ex.Message);
}

Con esto ya nuestras modificaciones sobre los registros en memoria seran actualizados de forma casi transparente para nosotros sin tener que realizar numerosas consultas.

Espero esto sea de utilidad a la hora de resolver problemas parecidos. De cualquiero forma dejo el link de MSDN donde encontre la informacion necesaria para realizar mi programa.

http://support.microsoft.com/kb/301248/es

1 comentario:

  1. Interesante aporte, se nota que has consultado bastante para resolver tu problema. Mucha de tu información me ha sido de gran ayuda para mi proyecto. ¡Gracias!

    ResponderEliminar