.net - name - ¿Por qué es DataTable más rápido que DataReader?
sqldatareader read (4)
2 cosas podrían ralentizarte.
Primero, no haría un "buscar ordinal por nombre" para cada columna, si está interesado en el rendimiento. Tenga en cuenta, la clase de "diseño" a continuación para hacerse cargo de esta búsqueda. Y los proveedores de diseño luego son legibles, en lugar de usar "0", "1", "2", etc. Y me permite codificar a una Interfaz (IDataReader) en lugar de al Concreto.
Segundo. Estás usando la propiedad ".Value". (y creo que esto hace una diferencia)
Obtendrá mejores resultados (IMHO) si utiliza el tipo de datos concreto "getters".
GetString, GetDateTime, GetInt32, etc, etc.
Aquí está mi código IDataReader típico a DTO / POCO.
[Serializable]
public partial class Employee
{
public int EmployeeKey { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime HireDate { get; set; }
}
[Serializable]
public class EmployeeCollection : List<Employee>
{
}
internal static class EmployeeSearchResultsLayouts
{
public static readonly int EMPLOYEE_KEY = 0;
public static readonly int LAST_NAME = 1;
public static readonly int FIRST_NAME = 2;
public static readonly int HIRE_DATE = 3;
}
public EmployeeCollection SerializeEmployeeSearchForCollection(IDataReader dataReader)
{
Employee item = new Employee();
EmployeeCollection returnCollection = new EmployeeCollection();
try
{
int fc = dataReader.FieldCount;//just an FYI value
int counter = 0;//just an fyi of the number of rows
while (dataReader.Read())
{
if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.EMPLOYEE_KEY)))
{
item = new Employee() { EmployeeKey = dataReader.GetInt32(EmployeeSearchResultsLayouts.EMPLOYEE_KEY) };
if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.LAST_NAME)))
{
item.LastName = dataReader.GetString(EmployeeSearchResultsLayouts.LAST_NAME);
}
if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.FIRST_NAME)))
{
item.FirstName = dataReader.GetString(EmployeeSearchResultsLayouts.FIRST_NAME);
}
if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.HIRE_DATE)))
{
item.HireDate = dataReader.GetDateTime(EmployeeSearchResultsLayouts.HIRE_DATE);
}
returnCollection.Add(item);
}
counter++;
}
return returnCollection;
}
//no catch here... see http://blogs.msdn.com/brada/archive/2004/12/03/274718.aspx
finally
{
if (!((dataReader == null)))
{
try
{
dataReader.Close();
}
catch
{
}
}
}
}
Así que hemos tenido un acalorado debate en el trabajo sobre qué ruta de DataAccess tomar: DataTable o DataReader.
DESCARGO DE RESPONSABILIDAD Estoy en el lado de DataReader y estos resultados han sacudido mi mundo.
Terminamos escribiendo algunos puntos de referencia para probar las diferencias de velocidad. En general, se acordó que un DataReader es más rápido, pero queríamos ver cuánto más rápido.
Los resultados nos sorprendieron. El DataTable fue consistentemente más rápido que el DataReader. A veces se acerca dos veces más rápido.
Así que me dirijo a ustedes, miembros de SO. ¿Por qué, cuando la mayoría de la documentación e incluso Microsoft, afirman que un DataReader es más rápido, nuestra prueba muestra lo contrario?
Y ahora para el código:
El arnés de prueba:
private void button1_Click(object sender, EventArgs e)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
DateTime date = DateTime.Parse("01/01/1900");
for (int i = 1; i < 1000; i++)
{
using (DataTable aDataTable = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveDTModified(date))
{
}
}
sw.Stop();
long dataTableTotalSeconds = sw.ElapsedMilliseconds;
sw.Restart();
for (int i = 1; i < 1000; i++)
{
List<ArtifactBusinessModel.Entities.ArtifactString> aList = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveModified(date);
}
sw.Stop();
long listTotalSeconds = sw.ElapsedMilliseconds;
MessageBox.Show(String.Format("list:{0}, table:{1}", listTotalSeconds, dataTableTotalSeconds));
}
Este es el DAL para el DataReader:
internal static List<ArtifactString> RetrieveByModifiedDate(DateTime modifiedLast)
{
List<ArtifactString> artifactList = new List<ArtifactString>();
try
{
using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts"))
{
using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast));
using (SqlDataReader reader = command.ExecuteReader())
{
int formNumberOrdinal = reader.GetOrdinal("FormNumber");
int formOwnerOrdinal = reader.GetOrdinal("FormOwner");
int descriptionOrdinal = reader.GetOrdinal("Description");
int descriptionLongOrdinal = reader.GetOrdinal("DescriptionLong");
int thumbnailURLOrdinal = reader.GetOrdinal("ThumbnailURL");
int onlineSampleURLOrdinal = reader.GetOrdinal("OnlineSampleURL");
int lastModifiedMetaDataOrdinal = reader.GetOrdinal("LastModifiedMetaData");
int lastModifiedArtifactFileOrdinal = reader.GetOrdinal("LastModifiedArtifactFile");
int lastModifiedThumbnailOrdinal = reader.GetOrdinal("LastModifiedThumbnail");
int effectiveDateOrdinal = reader.GetOrdinal("EffectiveDate");
int viewabilityOrdinal = reader.GetOrdinal("Viewability");
int formTypeOrdinal = reader.GetOrdinal("FormType");
int inventoryTypeOrdinal = reader.GetOrdinal("InventoryType");
int createDateOrdinal = reader.GetOrdinal("CreateDate");
while (reader.Read())
{
ArtifactString artifact = new ArtifactString();
ArtifactDAL.Map(formNumberOrdinal, formOwnerOrdinal, descriptionOrdinal, descriptionLongOrdinal, formTypeOrdinal, inventoryTypeOrdinal, createDateOrdinal, thumbnailURLOrdinal, onlineSampleURLOrdinal, lastModifiedMetaDataOrdinal, lastModifiedArtifactFileOrdinal, lastModifiedThumbnailOrdinal, effectiveDateOrdinal, viewabilityOrdinal, reader, artifact);
artifactList.Add(artifact);
}
}
}
}
}
catch (ApplicationException)
{
throw;
}
catch (Exception e)
{
string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast);
Logging.Log(Severity.Error, errMsg, e);
throw new ApplicationException(errMsg, e);
}
return artifactList;
}
internal static void Map(int? formNumberOrdinal, int? formOwnerOrdinal, int? descriptionOrdinal, int? descriptionLongOrdinal, int? formTypeOrdinal, int? inventoryTypeOrdinal, int? createDateOrdinal,
int? thumbnailURLOrdinal, int? onlineSampleURLOrdinal, int? lastModifiedMetaDataOrdinal, int? lastModifiedArtifactFileOrdinal, int? lastModifiedThumbnailOrdinal,
int? effectiveDateOrdinal, int? viewabilityOrdinal, IDataReader dr, ArtifactString entity)
{
entity.FormNumber = dr[formNumberOrdinal.Value].ToString();
entity.FormOwner = dr[formOwnerOrdinal.Value].ToString();
entity.Description = dr[descriptionOrdinal.Value].ToString();
entity.DescriptionLong = dr[descriptionLongOrdinal.Value].ToString();
entity.FormType = dr[formTypeOrdinal.Value].ToString();
entity.InventoryType = dr[inventoryTypeOrdinal.Value].ToString();
entity.CreateDate = DateTime.Parse(dr[createDateOrdinal.Value].ToString());
entity.ThumbnailURL = dr[thumbnailURLOrdinal.Value].ToString();
entity.OnlineSampleURL = dr[onlineSampleURLOrdinal.Value].ToString();
entity.LastModifiedMetaData = dr[lastModifiedMetaDataOrdinal.Value].ToString();
entity.LastModifiedArtifactFile = dr[lastModifiedArtifactFileOrdinal.Value].ToString();
entity.LastModifiedThumbnail = dr[lastModifiedThumbnailOrdinal.Value].ToString();
entity.EffectiveDate = dr[effectiveDateOrdinal.Value].ToString();
entity.Viewability = dr[viewabilityOrdinal.Value].ToString();
}
Este es el DAL para el DataTable:
internal static DataTable RetrieveDTByModifiedDate(DateTime modifiedLast)
{
DataTable dt= new DataTable("Artifacts");
try
{
using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts"))
{
using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast));
using (SqlDataAdapter da = new SqlDataAdapter(command))
{
da.Fill(dt);
}
}
}
}
catch (ApplicationException)
{
throw;
}
catch (Exception e)
{
string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast);
Logging.Log(Severity.Error, errMsg, e);
throw new ApplicationException(errMsg, e);
}
return dt;
}
Los resultados:
Para 10 iteraciones dentro del Arnés de Prueba.
Para 1000 iteraciones dentro del Arnés de Prueba.
Estos resultados son la segunda ejecución, para mitigar las diferencias debidas a la creación de la conexión.
No creo que se deba a la diferencia, pero intente algo como esto para eliminar algunas de las variables adicionales y las llamadas a funciones:
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
artifactList.Add(new ArtifactString
{
FormNumber = reader["FormNumber"].ToString(),
//etc
});
}
}
Veo tres cuestiones:
- la forma en que usa un DataReader niega su gran ventaja de un solo elemento en la memoria al convertirlo en una lista,
- está ejecutando el punto de referencia en un entorno que difiere significativamente de la producción de una manera que favorece a DataTable, y
- está invirtiendo tiempo en convertir el registro de DataReader en objetos de artefacto que no está duplicado en el código DataTable.
La principal ventaja de un DataReader es que no tiene que cargar todo en la memoria a la vez. Esto debería ser una gran ventaja para DataReader en las aplicaciones web, donde la memoria, en lugar de la CPU, es a menudo el cuello de botella, pero al agregar cada fila a una lista genérica lo ha negado. Eso también significa que incluso después de cambiar su código para usar solo un registro a la vez, es posible que la diferencia no se muestre en sus puntos de referencia porque los está ejecutando en un sistema con mucha memoria libre, lo que favorecerá a DataTable. Además, la versión de DataReader está dedicando tiempo a analizar los resultados en objetos de Artefacto que el DataTable aún no ha hecho.
Para solucionar el problema de uso del DataReader, cambie la List<ArtifactString>
a IEnumerable<ArtifactString>
todas partes, y en su DataReader DAL cambie esta línea:
artifactList.Add(artifact);
a esto:
yield return artifact;
Esto significa que también debe agregar código que se repita sobre los resultados a su arnés de prueba DataReader para mantener las cosas en orden.
No estoy seguro de cómo ajustar el punto de referencia para crear un escenario más típico que sea justo tanto para DataTable como para DataReader, excepto para construir dos versiones de su página, y servir cada versión durante una hora bajo una carga de nivel de producción similar. que tenemos presión de memoria real ... hacer algunas pruebas A / B reales. Además, asegúrese de cubrir la conversión de las filas de DataTable a Artefactos ... y si el argumento es que necesita hacer esto para un DataReader, pero no para un DataTable, eso es simplemente erróneo.
SqlDataAdapter.Fill
llama a SqlCommand.ExecuteReader con CommandBehavior.SequentialAccess
establecido. Tal vez eso sea suficiente para hacer la diferencia.
Como nota aparte, veo que su implementación de IDbReader
almacena en caché los ordinales de cada campo por razones de rendimiento. Una alternativa a este enfoque es usar la clase DbEnumerator .
DbEnumerator
almacena en caché el nombre de un campo -> diccionario ordinal internamente, por lo que le ofrece gran parte de la ventaja de rendimiento de usar ordinales con la simplicidad de usar nombres de campo:
foreach(IDataRecord record in new DbEnumerator(reader))
{
artifactList.Add(new ArtifactString() {
FormNumber = (int) record["FormNumber"],
FormOwner = (int) record["FormOwner"],
...
});
}
o incluso:
return new DbEnumerator(reader)
.Select(record => new ArtifactString() {
FormNumber = (int) record["FormNumber"],
FormOwner = (int) record["FormOwner"],
...
})
.ToList();