前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >DataReader类型化数据读取与装箱性能研究

DataReader类型化数据读取与装箱性能研究

作者头像
用户1177503
发布于 2020-06-19 02:21:40
发布于 2020-06-19 02:21:40
1.6K00
代码可运行
举报
文章被收录于专栏:程序员的SOD蜜程序员的SOD蜜
运行总次数:0
代码可运行

前言

在各种ORM框架或者SQL映射框架(例如MyBatis,SOD框架之SQL-MAP功能)中,都有将查询的结果映射为内存对象的需求,包括映射到实体类、简单类型(例如Java的POJO,.NET的POCO)的对象。在.NET中,这个过程可以通过ADO.NET的DataReader对象来读取数据,然后将读取的数据映射到内存对象。本篇文章来讨论下不同方式的数据读取方式对性能的影响。

在写这篇文章之前,我在想现在都2020年全民奔小康了,除了微软官方的EF框架之外,各种ORM框架层出不穷,连笔者的SOD框架都诞生15年了,还有必要研究这么Low的问题吗?后来想了想,自己写博客主要是总结经验,记录问题分析过程的,虽然笔者在2013年就做过一个测试,写了《用事实说话,成熟的ORM性能不是瓶颈,灵活性不是问题:EF5.0、PDF.NET5.0、Dapper原理分析与测试手记》,但这篇文章已经过去6年多时间了,.NET框架都发展到跨平台的.NET Core了,现在Dapper更火了,基于Emit和表达式树的ORM轮子层出不穷,性能和易用性都不错,这些优秀的ORM框架获得了很高的关注,而SOD框架一直很低调,因为它一直没用采用Emit和表达式树技术,也没有采用反射,而是最原始的DataReader的非类型化数据读取方式,性能上可能比不上这些ORM框架,但会有多大的差异呢?SOD框架一直强调自己不仅仅是一个ORM框架,ORM仅仅是它的一个功能组件,不过大家既然都这么强调性能,于是决定重新测试一下DataReader的非类型化数据读取与类型化数据读取的性能差异,演示下正确使用两者的方式。

映射对象

下面的测试方法都是将数据库同样的数据通过DataReader读取出来映射到不同的对象中,本篇文章测试用来映射的对象一个是SOD框架的实体类,一个是普通的DTO对象,DTO是POCO的一种。下面是这两种对象的定义:

SOD实体对象类User的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public class User : EntityBase
    {
        public User()
        {
            TableName="Tb_User1";
           
            IdentityName = "UserID";
            PrimaryKeys.Add("UserID");
        }

        /// <summary>
        /// 设置字段名数组,如果不实现该方法,框架会自动反射获取到字段名数组,因此从效率考虑,建议实现该方法
        /// </summary>
        protected override void SetFieldNames()
        {
            PropertyNames = new string[] { "UserID", "Name", "Pwd", "RegistedDate" };
        }

        /// <summary>
        /// 获取实体类全局唯一标识;重写该方法,可以加快访问效率
        /// </summary>
        /// <returns></returns>
        public override string GetGolbalEntityID()
        {
            //使用工具-》创建GUID 生成
            return "F1344072-AB1E-4BCF-A28C-769C7C4AA06B";
        }

        public int ID
        {
            get { return getProperty<int>("UserID"); }
            set { setProperty("UserID", value); }
        }

        public string Name
        {
            get { return getProperty<string>("Name"); }
            set { setProperty("Name", value, 50); }
        }

        public string Pwd
        {
            get { return getProperty<string>("Pwd"); }
            set { setProperty("Pwd", value, 50); }
        }

        public DateTime RegistedDate
        {
            get { return getProperty<DateTime>("RegistedDate"); }
            set { setProperty("RegistedDate", value); }
        }

    }

DTO类 UserDto的定义,跟实体类User完全一样的属性名称和属性类型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public class UserDto
    {
        public UserDto()
        {
        }

        public int UserID
        { get; set; }

        public string Name
        { get; set; }

        public string Pwd
        { get; set; }

        public DateTime RegistedDate
        { get; set; }
    }

下面开始不同的查询方式测试。

1,手写查询映射

测试方案为将DataReader读取出来的数据手工逐一映射到一个POCO对象的属性上,例如下面映射到UserDto对象上。根据查询时候的SQL语句中指定的数据列的顺序和类型来使用DataReader是效率最高的方式,也就是DataReader类型化数据读取方法,使用字段索引而不是字段名称来读取数据的方式,如下面示例代码中的reader.GetInt32(0) :

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//手写DataReader查询
private static long HandQuery(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
    watch.Restart();
    string sql = "select  UserID, Name, Pwd, RegistedDate from Tb_User1";
    IList<UserDto> list = db.ExecuteMapper(sql).MapToList<UserDto>(reader => new UserDto
    {
        UserID = reader.IsDBNull(0)? default(int): reader.GetInt32(0),
        Name = reader.IsDBNull(1) ? default(string) : reader.GetString(1),
        Pwd = reader.IsDBNull(2) ? default(string) : reader.GetString(2),
        RegistedDate = reader.IsDBNull(3) ? default(DateTime) : reader.GetDateTime(3)
    });
    watch.Stop();
    Console.WriteLine("HandQuery List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
    return watch.ElapsedMilliseconds;
}

代码说明:

方法的第一个参数db是SOD框架的AdoHelper对象,它是对各种数据库进行访问的一个提供程序类,封装了ADO.NET各种对象的访问,包括自动管理连接、执行查询、管理事务和记录日志等功能。在当前测试程序中这里它的实例对象是SQL Server访问提供程序。AdoHelper对象的ExecuteMapper方法将数据查询结果封装成一个DataReaderMapper对象,然后可以使用该对象的MapToList方法使用DataReader对象的类型化数据读取方法,将读取的值赋值给要映射的对象的属性,例如这里的UserDto对象。需要注意的是,在调用DataReader的类型化数据读取方法的时候,必须先判断当前位置的数据是否空数据(DBNull),否则会出错。例如上面的示例代码中,如果索引位置0的数据为空数据,则给UserDto对象的UserID属性赋值int类型的默认值0。MapToList方法会读取结果集的所有数据,读取完后自动关闭连接。

AdoHelper对象的封装比较简单,并且上面的查询会查询Tb_User1表的全部10万条数据,所以在讨论查询性能的时候,可以认为绝大部分时间都是在处理DataReader读取数据的问题,并且还采用了比字段名定位数据读取位置更高效的字段索引读取的方式,因此可以认为HandQuery方法的查询等同于最高效的手写查询方式。

2,映射数据到POCO对象

上面的手写测试代码看起来简单,但是必须清楚当前读取字段的索引位置和当前字段的数据类型,当SQL比较复杂或者SQL语句不在当前方法内设置的,那么要写这种代码就很困难了并且还容易出错,所以手写代码使用类型化数据读取和对象属性映射就是一个费力不讨好的“体力活”,除非对性能有极高要求否则一般人都不会这样直接处理查询映射。要解决这个问题我们可以使用反射、Emit或者表达式树来动态生成这种跟手写查询一样的代码。

SOD框架并没有使用上面的几种方式来模拟手写查询代码,而是使用DataReader的非类型化数据读取方式,再结合委托和缓存的方式来高效访问要映射的对象,例如当前要映射的POCO对象。这个过程可以通过AdoHelper对象的QueryList方法来完成,请看下面的示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private static long QueryPOCO(AdoHelper db, System.Diagnostics.Stopwatch watch)
 {
     watch.Restart();
     string sql = "select  UserID, Name, Pwd, RegistedDate from Tb_User1";
     IList<UserDto> list = db.QueryList<UserDto>(sql);
     watch.Stop();
     Console.WriteLine("QueryPOCO List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
     return watch.ElapsedMilliseconds;
 }

代码说明:

使用AdoHelper对象的QueryList方法要求要映射的对象的属性名字和查询结果集的字段名必须严格一致,如果名字不一致,可以在SQL语句中使用字段别名。QueryList方法可以接受多个参数,除了第一个参数是要执行的SQL语句之外,其它参数可以是SQL语句中的“参数”。所以这个查询方式非常简单,只需要一行代码就可完成查询,类似Dapper的功能,所以这个功能算是SOD框架中的“微型ORM”。

下面是QueryList方法的定义和使用示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /// <summary>
  /// 根据SQL格式化串和可选的参数,直接查询结果并映射到POCO 对象
  /// <example>
  /// <code>
  /// <![CDATA[
  /// //假设UserPoco 对象跟 Table_User 表是映射的相同结构
  /// AdoHelper dbLocal = new SqlServer();
  /// dbLocal.ConnectionString = "Data Source=.;Initial Catalog=LocalDB;Integrated Security=True";
  /// var list=dbLoal.QueryList<UserPoco>("SELECT UID,Name FROM Table_User WHERE Sex={0} And Height>={1:5.2}",1, 1.60M);
  /// ]]>
  /// </code>
  /// </example>
  /// </summary>
  /// <typeparam name="T">POCO 对象类型</typeparam>
  /// <param name="sqlFormat">SQL格式化串</param>
  /// <param name="parameters">可选的参数</param>
  /// <returns>POCO 对象列表</returns>
  public  List<T> QueryList<T>(string sqlFormat, params object[] parameters) where T : class, new()
  {
      IDataReader reader = FormatExecuteDataReader(sqlFormat, parameters);
      return QueryList<T>(reader);
  }

如上代码所示,方法第一个参数是一个SQL格式化字符串,在这个格式化字符串中可以有多个参数,就像string.Format方法的使用一样。例如上面方法的注释中查询条件Sex字段的参数和Height字段的参数,其中Height字段的参数的格式是精度为5,小数位数为2的浮点数。

上面的方法调用了QueryList泛型方法来处理DataReader对象读取的数据,下面看看它的实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/// <summary>
/// 采用快速的方法,将数据阅读器的结果映射到一个POCO类的列表上
/// </summary>
/// <typeparam name="T">POCO类类型</typeparam>
/// <param name="reader">抽象数据阅读器</param>
/// <returns>POCO类的列表</returns>
public static List<T> QueryList<T>(IDataReader reader) where T : class, new()
{
    List<T> list = new List<T>();
    using (reader)
    {
        if (reader.Read())
        {
            int fcount = reader.FieldCount;
            //使用类型化委托读取正确的数据,解决MySQL等数据库可能的问题,感谢网友 @卖女孩的小肥羊 发现此问题
            Dictionary<Type, MyFunc<IDataReader, int, object>> readerDelegates = DataReaderDelegate();
            MyFunc<IDataReader, int, object>[] getDataMethods = new MyFunc<IDataReader, int, object>[fcount];

            INamedMemberAccessor[] accessors = new INamedMemberAccessor[fcount];
            DelegatedReflectionMemberAccessor accessorMethod = new DelegatedReflectionMemberAccessor();
            for (int i = 0; i < fcount; i++)
            {
                accessors[i] = accessorMethod.FindAccessor<T>(reader.GetName(i));
                //修改成从POCO实体类的属性上来获取DataReader类型化数据访问的方法,而不是之前的DataReader 的字段的类型
                if (!readerDelegates.TryGetValue(accessors[i].MemberType, out getDataMethods[i]))
                {
                    getDataMethods[i] = (rd, ii) => rd.GetValue(ii);
                }
            }
            
            do
            {
                T t = new T();
                for (int i = 0; i < fcount; i++)
                {
                    if (!reader.IsDBNull(i))
                    {
                        MyFunc<IDataReader, int, object> read = getDataMethods[i];
                        object value=read(reader,i);
                        accessors[i].SetValue(t, value);
                    }
                        
                }
                list.Add(t);
            } while (reader.Read());
        }
    }
    return list;
}

在上面的代码中的do循环之前,为要映射的POCO对象的每个属性访问器构建了一个MyFunc<IDataReader, int, object> 委托,该委托实际上来自于SOD框架预定义的一个处理DataReader类型化数据读取的委托,为了通用,上面这个委托方法返回值定义成了object类型,这样在实际调用的时候会进行“装箱”操作,也就是上面方法的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 object value=read(reader,i);
 accessors[i].SetValue(t, value);

之所以要进行装箱,是因为属性访问器方法SetValue需要一个object类型参数。

返回DataReader类型化数据读取方法委托的DataReaderDelegate方法定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private static Dictionary<Type, MyFunc<IDataReader, int, object>> dictReaderDelegate = null;
 private static Dictionary<Type, MyFunc<IDataReader, int, object>> DataReaderDelegate()
 {
     if (dictReaderDelegate == null)
     {
        Dictionary<Type, MyFunc<IDataReader, int, object>> dictReader = new Dictionary<Type, MyFunc<IDataReader, int, object>>();
        dictReader.Add(typeof(int), (reader, i) => reader.GetInt32(i));
        dictReader.Add(typeof(bool), (reader, i) => reader.GetBoolean(i));
        dictReader.Add(typeof(byte), (reader, i) => reader.GetByte(i));
        dictReader.Add(typeof(char), (reader, i) => reader.GetChar(i));
        dictReader.Add(typeof(DateTime), (reader, i) => reader.GetDateTime(i));
        dictReader.Add(typeof(decimal), (reader, i) => reader.GetDecimal(i));
        dictReader.Add(typeof(double), (reader, i) => reader.GetDouble(i));
        dictReader.Add(typeof(float), (reader, i) => reader.GetFloat(i));
        dictReader.Add(typeof(Guid), (reader, i) => reader.GetGuid(i));
        dictReader.Add(typeof(System.Int16), (reader, i) => reader.GetInt16(i));
        dictReader.Add(typeof(System.Int64), (reader, i) => reader.GetInt64(i));
        dictReader.Add(typeof(string), (reader, i) => reader.GetString(i));
        dictReader.Add(typeof(object), (reader, i) => reader.GetValue(i));

        dictReaderDelegate = dictReader;
    }
   return dictReaderDelegate;
}

3,SOD框架的DataReader非类型化数据读取

SOD框架的实体类查询方法直接使用了DataReader非类型化数据读取方式,一次性将一行数据读取到一个object[]对象数组中,SOD实体类将直接使用这个object[]对象数组,这使得数据映射过程可以大大简化代码,并且取得不错的效率。下面是测试实体类查询方法的示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static long EntityQuery(AdoHelper db, System.Diagnostics.Stopwatch watch)
 {
     watch.Restart();
     User user = new User();
     OQL q = OQL.From(user).Select(user.ID, user.Name, user.Pwd, user.RegistedDate).END;
     //q.Limit(5000);
     var list = EntityQuery<User>.QueryList(q, db);
     watch.Stop();
     Console.WriteLine("SOD QueryList List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
     return watch.ElapsedMilliseconds;
 }

下面是QueryList方法有关数据读取和映射的具体实现部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  /// <summary>
  /// 根据数据阅读器对象,查询实体对象集合(注意查询完毕将自动释放该阅读器对象)
  /// </summary>
  /// <param name="reader">数据阅读器对象</param>
  /// <param name="tableName">指定实体类要映射的表名字,默认不指定</param>
  /// <returns>实体类集合</returns>
  public static List<T> QueryList(System.Data.IDataReader reader,string tableName)
  {
      List<T> list = new List<T>();
      if (reader == null)
          return list;
      using (reader)
      {
          if (reader.Read())
          {
              int fcount = reader.FieldCount;
              string[] names = new string[fcount];

              for (int i = 0; i < fcount; i++)
                  names[i] = reader.GetName(i);
              T t0 = new T();
              if (!string.IsNullOrEmpty(tableName))
                  t0.MapNewTableName(tableName);
              t0.PropertyNames = names;
              do
              {
                  object[] values = new object[fcount];
                  reader.GetValues(values);

                  T t = (T)t0.Clone(false );

                  //t.PropertyNames = names;
                  t.PropertyValues = values;

                  list.Add(t);

              } while (reader.Read());

          }
      }
      return list;
  }

上面的方法直接使用了DataReader对象的非类型化数据读取方法GetValues,将数据读取到values数组对象中。在当前QueryList方法中没用对DataReader对象读取的数据进行装箱,但是这种方式相比测试方式1的手写映射方式性能还是要低,猜测方法内部进行了复杂的处理,否则无法解释测试方式2测试代码中类型化数据读取后数据进行装箱后供数据访问器使用,测试2的测试性能仍然高于当前测试方式3,但不会有太大的性能差距。

4,类型化读取到数组元素中

如果DataReader对象类型化读取速度一定比非类型化数据读取方法GetValues快,那么可以尝试将类型化数据读取的值装箱到数组元素中,这样有可能提高SOD框架现有的QueryList方法的性能。下面模拟对QueryList方法进行修改,使得DataReader对象类型化读取到数组元素中。请看下面的示例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private static long EntityQuery2(AdoHelper db, System.Diagnostics.Stopwatch watch)
 {
     watch.Restart();
     string sql = "select  UserID, Name, Pwd, RegistedDate from Tb_User1";

     string tableName = "";
     User entity = new User();
     IDataReader reader = db.ExecuteDataReader(sql);
     List<User> list = new List<User>();
     using (reader)
     {
         if (reader.Read())
         {
             int fcount = reader.FieldCount;
             string[] names = new string[fcount];

             for (int i = 0; i < fcount; i++)
                 names[i] = reader.GetName(i);
             User t0 = new User();
             if (!string.IsNullOrEmpty(tableName))
                 t0.MapNewTableName(tableName);
             //正式,下面放开
             // t0.PropertyNames = names;
             //
             Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetInt32(i); };
             Action< int, object[]> readString = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetString(i); };
             Action< int, object[]> readDateTime = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetDateTime(i); };
             Action< int, object[]>[] readerActions = {
                      readInt,readString,readString,readDateTime
               };
             //
             do
             {
                 User item = (User)t0.Clone(false);
                 for (int i = 0; i < readerActions.Length; i++)
                 {
                     readerActions[i]( i, item.PropertyValues);
                 }

                 list.Add(item);
             } while (reader.Read());

         }
     }

     //return list;
     watch.Stop();
     Console.WriteLine("EntityQuery2 List (10000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
     return watch.ElapsedMilliseconds;
 }

测试过程

以上4种测试方法准备完毕,下面准备测试数据,使用SQL Server Express LocalDB 创建一个数据库文件,在此文件数据库中创建一个User实体类对应的数据表,然后插入10万条数据,这个功能可以通过SOD框架下面的代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private static void InitData(AdoHelper db, System.Diagnostics.Stopwatch watch)
 {
     //自动创建数据库和表
     LocalDbContext context = new LocalDbContext();
     Console.WriteLine("需要初始化数据吗?(Y/N) ");
     string input= Console.ReadLine();
     if (input.ToLower() != "y") return;
     Console.WriteLine("正在初始化数据,请稍后。。。。");
     context.TruncateTable<User>();
     Console.WriteLine("...");
     watch.Restart();
     List<User> batchList = new List<User>();
     for (int i = 0; i < 100000; i++)
     {
         User zhang_yeye = new User() { ID = 1000 + i, Name = "zhang yeye" + i, Pwd = "pwd" + i ,RegistedDate =DateTime.Now };
         //count += EntityQuery<User>.Instance.Insert(zhang_yeye);//采用泛型 EntityQuery 方式插入数据
         batchList.Add(zhang_yeye);
     }
     watch.Stop();
     Console.WriteLine("准备数据 耗时:(ms)" + watch.ElapsedMilliseconds);

     watch.Restart();
     int count = EntityQuery<User>.Instance.QuickInsert(batchList);
     watch.Stop();
     Console.WriteLine("QuickInsert List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
     System.Threading.Thread.Sleep(1000);
 }

代码说明:

上面的方法中首先初始化数据库,通过DbContext对象自动创建数据表,并且通过TruncateTable 方法快速清除原来的测试数据。接着在内存中添加10万条数据,然后将它使用QuickInsert方法快速插入到数据库。

下面就可以给出完整的测试过程了,直接看代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void Main(string[] args)
{
    System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
    watch.Start();
    AdoHelper db = MyDB.GetDBHelperByConnectionName("local");
    InitData(db, watch);

    long[] useTime1 = new long[10];
    long[] useTime2 = new long[10];
    long[] useTime3 = new long[10];
    long[] useTime4 = new long[10];

    for (int i = 0; i < 10; i++)
    {
        useTime1[i]= HandQuery(db, watch);
        System.Threading.Thread.Sleep(1000); //便于观察CPU、内存等资源变化

        useTime2[i] = QueryPOCO(db, watch);
        System.Threading.Thread.Sleep(1000);

        useTime3[i] = EntityQuery(db, watch);
        System.Threading.Thread.Sleep(1000);

        useTime4[i] = EntityQuery2(db, watch);
        System.Threading.Thread.Sleep(1000);

        Console.WriteLine("run test No.{0},sleep 1000 ms", i + 1);
        Console.WriteLine();
    }
    //去掉热身的第一次
    useTime1[0] = 0;
    useTime2[0] = 0;
    useTime3[0] = 0;
    useTime4[0] = 0;
    Console.WriteLine("Avg HandQuery={0} ms, \r\n Avg QueryPOCO={1} ms, \r\n Avg SOD EntityQuery={2} ms,\r\n Avg EntityQuery2={3} ms"
        , useTime1.Average(),useTime2.Average(),useTime3.Average(), useTime4.Average());
    
    Console.ReadLine();
}

测试过程去掉第一次循环测试的“热身”过程,计算剩余9次不同方式的平均执行时间,下面是在笔者笔记本电脑(Intel i7-4720HQ CPU 2.6GHz,12G RAM,普通硬盘)的测试结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
需要初始化数据吗?(Y/N)
y
正在初始化数据,请稍后。。。。
...
准备数据 耗时:(ms)225
QuickInsert List (100000 item) 耗时:(ms)5363
HandQuery List (100000 item) 耗时:(ms)158
QueryPOCO List (100000 item) 耗时:(ms)188
SOD QueryList List (100000 item) 耗时:(ms)251
EntityQuery2 List (10000 item) 耗时:(ms)281
run test No.1,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)139
QueryPOCO List (100000 item) 耗时:(ms)192
SOD QueryList List (100000 item) 耗时:(ms)194
EntityQuery2 List (10000 item) 耗时:(ms)283
run test No.2,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)156
QueryPOCO List (100000 item) 耗时:(ms)177
SOD QueryList List (100000 item) 耗时:(ms)224
EntityQuery2 List (10000 item) 耗时:(ms)289
run test No.3,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)183
QueryPOCO List (100000 item) 耗时:(ms)179
SOD QueryList List (100000 item) 耗时:(ms)213
EntityQuery2 List (10000 item) 耗时:(ms)265
run test No.4,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)172
QueryPOCO List (100000 item) 耗时:(ms)179
SOD QueryList List (100000 item) 耗时:(ms)226
EntityQuery2 List (10000 item) 耗时:(ms)273
run test No.5,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)172
QueryPOCO List (100000 item) 耗时:(ms)211
SOD QueryList List (100000 item) 耗时:(ms)192
EntityQuery2 List (10000 item) 耗时:(ms)229
run test No.6,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)202
QueryPOCO List (100000 item) 耗时:(ms)229
SOD QueryList List (100000 item) 耗时:(ms)191
EntityQuery2 List (10000 item) 耗时:(ms)240
run test No.7,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)190
QueryPOCO List (100000 item) 耗时:(ms)177
SOD QueryList List (100000 item) 耗时:(ms)218
EntityQuery2 List (10000 item) 耗时:(ms)274
run test No.8,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)166
QueryPOCO List (100000 item) 耗时:(ms)191
SOD QueryList List (100000 item) 耗时:(ms)197
EntityQuery2 List (10000 item) 耗时:(ms)229
run test No.9,sleep 1000 ms

HandQuery List (100000 item) 耗时:(ms)179
QueryPOCO List (100000 item) 耗时:(ms)192
SOD QueryList List (100000 item) 耗时:(ms)213
EntityQuery2 List (10000 item) 耗时:(ms)253
run test No.10,sleep 1000 ms

Avg HandQuery=155.9 ms,
 Avg QueryPOCO=172.7 ms,
 Avg SOD EntityQuery=186.8 ms,
 Avg EntityQuery2=233.5 ms

测试结果说明,SOD框架的QueryPOCO“微型ORM”功能性能不错,虽然有数据装箱过程,但仍然接近手写代码数据映射的方式。SOD框架最常用的EntityQuery实体查询性能接近于QueryPOCO方式,而本次的测试方法4尝试将类型化数据读取到object数组对象也有装箱过程,性能却远低于EntityQuery实体查询方式。那么测试方法4的EntityQuery2方法中如果不装箱,直接采用读取指定位置数据为object类型能否性能明显提升呢?比如将方法中下面的代码:

Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) oi = DBNull.Value; else oi = reader.GetInt32(i); };

修改为:

Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) oi = DBNull.Value; else oi = reader.GetValue(i); };

经测试,修改前后性能没用明显的改善,两者性能基本相同。看来DataReader对象是否使用类型化数据读取对性能没用明显的影响,也就是读取的数据是否装箱对于ORM的数据映射性能没有明显影响,ORM查询过程中对性能影响最大的应该是数据库,而不是数据装箱。测试方法4还说明了,将DataReader的数据一次性读取到object[]对象数组中,性能要明显高于逐字段读取,不管是类型化读取还是非类型化读取。

这次测试也说明,SOD框架的ORM性能与手写代码查询映射的性能接近,没有明显的差距,SOD框架仍然是一个简单、高效、可靠的,值得使用的数据开发框架。本次测试的全部代码都在SOD项目解决方案的“SODTest”程序集项目中,源码仓库地址:https://github.com/znlgis/sod

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
MySQL索引实现原理分析
目前大部分数据库系统及文件系统都采用B-Tree(B树)或其变种B+Tree(B+树)作为索引结构。B+Tree是数据库系统实现索引的首选数据结构。在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。MyISAM索引实现MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:image.png这里设表一共有三列,假设我
全栈程序员站长
2022/07/23
4900
MySQL索引实现原理分析
MySQL索引底层实现原理 & MyISAM非聚簇索引 vs. InnoDB聚簇索引
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。
一个会写诗的程序员
2019/10/28
1.4K0
MySQL索引底层实现原理 & MyISAM非聚簇索引 vs. InnoDB聚簇索引
「Mysql索引原理(六)」聚簇索引
本节课主要关注InnoDB,但是这里讨论的原理对于任何支持聚簇索引的存储引擎都是适用的。
源码之路
2020/09/04
3.1K0
「Mysql索引原理(六)」聚簇索引
MySQL索引特性
MySQL的服务器,本质是在内存中的,所有的数据库的CURD操作,全都是在内存中进行的,所以索引也是如此。索引的作用是提高查找的效率。
每天都要进步呀
2023/10/16
1950
MySQL索引特性
MySQL索引凭什么让查询效率提高这么多?
我相信大家在数据库优化的时候都会说到索引,我也不例外,大家也基本上能对数据结构的优化回答个一二三,以及页缓存之类的都能扯上几句,但是有一次阿里P9的一个面试问我:你能从计算机层面开始说一下一个索引数据加载的流程么?(就是想让我聊IO)
敖丙
2020/09/14
8340
MySQL索引凭什么让查询效率提高这么多?
MySQL的B+tree索引实现原理
官方定义:索引(Index)是帮助MySQL高效获取数据的数据结构,即索引是数据结构。 其出现就是为了提高数据查询效率,就像书的目录。
JavaEdge
2021/02/22
6200
MySQL的B+tree索引实现原理
2024年java面试准备--mysql(1)
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库中表的数据。索引的实现通常使用B树和变种的B+树(MySQL常用的索引就是B+树)。除了数据之外,数据库系统还维护为满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这种数据结构就是索引。简言之,索引就类似于书本,字典的目录。
终有救赎
2023/10/16
2040
2024年java面试准备--mysql(1)
【MySQL(2)| MySQL索引机制】
索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要。
周三不加班
2019/09/03
1.1K0
【MySQL(2)| MySQL索引机制】
面试又给我问到MySQL索引【索引的实现原理】
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,B+Tree索引,哈希索引,全文索引等等,
码农编程进阶笔记
2021/07/20
1.7K0
面试又给我问到MySQL索引【索引的实现原理】
mysql学习之优化总结(2)--索引的那些事
上一篇文章我们在研究MySQL查询过程的查询优化步骤中提到过优化索引可以优化查询优化的过程,索引到底是什么?它在查询过程中是一个怎样的角色?索引适用于什么场景?我们怎么用好它呢,这一节我们一起来深入了解下索引,理解索引相关的数据结构和算法,理解它的原理,帮助我们更好的使用索引。
王辅佳
2018/10/27
7530
深入理解硬盘原理,Mysql索引底层数据结构与算法的来龙去脉(多图)
盘面号:扇区所在的磁头(或盘面) 柱面号:磁道,确定磁头的径向方向。扇区号:在磁道上的位置。也叫块号。确定了数据在盘片圆圈上的位置。
Java宝典
2021/01/14
1K0
深入理解硬盘原理,Mysql索引底层数据结构与算法的来龙去脉(多图)
MySQL十一:索引基本原理
在上一篇《索引基础知识回顾》中提到索引按照存储结构划分有B-Tree索引、Hash索引、B+Tree索引类型,接下来就学习一下这几种索引结构以及在实际存储引擎中的使用情况
云扬四海
2022/09/26
6420
深入浅出索引
索引,一种强大的存在;不管是什么行业,数据都是根基,终将落盘固化,提供各方检索查询,之前整理了一篇《深入浅出spring事务》,你可以推脱不使用事务,但索引是不可或缺的必备知识点
码农戏码
2021/03/23
5860
MySQL索引的概念与好处
在讲述索引之前,我们需要认识MySQL的存储引擎。目前,MySQL的存储引擎共有MyISAM 、InnoDB、Memory三种,其中,InnoDB在MySQL5.5后成为默认引擎,也就是说,我们后面所讲述的引擎都是基于InnoDB引擎的。三者所支持的索引类型有所不同,但都实现了B+树索引
阿珍
2024/09/30
1560
MySQL索引的概念与好处
MySQL索引原理——B树
1、MyISAM是MySQL 5.5之前版本默认的存储引擎,从5.5之后,InnoDB开始成为MySQL默认的存储引擎。MyISAM和InnoDB都是使用B+树实现主键索引、唯一索引和非主键索引。
saintyyu
2021/11/22
6630
MySQL索引原理——B树
【MySQL高级】索引
1.中央处理器(英文Central Processing Unit,CPU)是一台计算机的运算核心和控制核心。CPU、内部存储器和输入/输出设备是电子计算机三大核心部件。其功能主要是解释计算机指令以及处理计算机软件中的数据。
陶然同学
2023/02/24
4540
【MySQL高级】索引
MySQL索引底层的数据结构
这里有一篇关于存储引擎的文章:https://blog.csdn.net/qq_41618510/article/details/84680226
Java廖志伟
2021/01/29
6450
MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题。特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎 对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。为了避免混乱,本文将只关注于BTree索 引,因为这是平常使用MySQL时主要打交道的索引,至于哈希索引和全文索引本文暂不讨论。 数据结构及算法基础 索引的本质 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到
wangxl
2018/03/08
1.2K0
MySQL索引背后的数据结构及算法原理
MySQL 索引原理 图文讲解
在数据库中,索引可以理解为是一种单独的,物理的对数据库表中的一列或者多列的值进行排序的一种存储结构。它的作用是能让我们快速检索到想要的数据,好比字典的目录,通过目录的页码能快速找到我们想查找的内容。
陈皮的JavaLib
2021/03/23
8830
MySQL 索引原理 图文讲解
Mysql高级
1.中央处理器(英文Central Processing Unit,CPU)是一台计算机的运算核心和控制核心。CPU、内部存储器和输入/输出设备是电子计算机三大核心部件。其功能主要是解释计算机指令以及处理计算机软 件中的数据。 CPU核心组件: 1.算术逻辑单元(Arithmetic&logical Unit)是中 央处理器(CPU)的执行单元,是所有中央处理器的核 心组成部分,由"And Gate"(与门) 和"Or Gate"(或门)构成的算术逻辑单元,主要功能是进行二位元的算术运算,如加减乘(不包括整数除法)。 2.PC:负责储存内存地址,该地址指向下一条即将执行的指令,每解释执行完一条指令,pc寄存器的值 就会自动被更新为下一条指令的地址。 3.寄存器(Register)是CPU内部的元件,所以在寄存器之间的数据传送非常快。 用途:1.可将寄存器内的数据执行算术及逻辑运算。 2.存于寄存器内的地址可用来指向内存的某个位置,即寻址。 3.可以用来读写数据到电脑的周边设备。4.Cache:缓存
Maynor
2021/12/27
4400
Mysql高级
相关推荐
MySQL索引实现原理分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文