提升SQL性能--《数据访问宝典》读书笔记
最近读了John Goodson和Robert A.Steward的《数据访问宝典》,里面提到了很多优化性能的方法,下面做了个读书笔记,与园友们分享。
SQL性能参数
响应时间:在数据请求和数据返回这个过程中经历的时间。
吞吐量:在一段时间内从发送程序向接收程序传输的数据的数量。
可伸缩性:当同时访问数据库的用户数量增加时,应用程序维持可以接受的响应时间和吞吐量的能力。
编写SQL代码原则
减少网络通信量
限制磁盘I/O
优化应用程序和驱动程序之间的交互
简化请求
.NET程序优化要点
·使用连接池。
连接池是应用程序能够重复使用的一个或多个数据库连接的高速缓存。
创建一个新的数据库连接所耗费的时间主要取决于网络的速度以及应用程序和数据库服务器的(网络)距离,而且这个过程通常是一个很耗时的过程。而采用数据库连接池后,数据库连接请求可以直接通过连接池满足而不需要为该请求重新连接、认证到数据库服务器,减少建立物理连接所需要的相关资源开销,这样就节省了时间。
适用:
应用程序运行在应用服务器上时。因为这意味着多个用户会同时使用应用程序。
应用程序具有多个用户,并且数据库服务器具有足够的内存管理最大数量的连接。
不适用:
应用程序每天重新启动很多次。每次启动会在连接池中建立一些连接,这会降低性能。
单用户应用程序。
对于.NET应用程序而言,默认为允许连接池。当然,如果你可以在SQLConnection对象的连接字符串中加进Pooling=false禁止连接池的使用。
·高效的建立和断开连接。因为建立连接需要耗费内存,如果待垃圾收集器清除不再使用的连接,连接占用内存的时间比所需要的时间更长,从而削弱应用程序的性能。
·关闭事务的自动提交。使用tran.Commit()方法进行手动提交,因为每个提交操作都会耗费大量的磁盘I/O,并且需要在驱动程序和数据库服务器之间进行额外的网络往返,使用手动提交,应用程序可以控制何时提交数据库工作,从而显著提高系统的性能。
·避免使用CommandBuilder对象。因为并发操作的限制,CommandBuilder经常生成效率低下的SQL语句,此外CommandBuilder对象在运行时生成语句,每次调用DataAdapter.Update方法时,CommandBuilder都会分析结果集的内容,并为DataAdapter生成Insert/Update/Delete语句,当显示指定了Insert/Update/Delete语句时,就不需要这个额外的处理步骤。·如果需要检索大量只读数据,选择DataReader,如果需要插入、更新或删除数据,以任何顺序返回数据,才使用DataSet对象。虽然DataSet灵活,但是随之而来的是它要消耗大量的内存。
DataReader对象针对快速检索大量数据进行了优化,数据时只读的,并且只能以向前的顺序读取数据,内存使用量最小。DataSet对象是代表整个数据结果集的数据缓存,可以修改DataSet中的数据,并可以使用任意顺序获取数据。
·使用GetXXX方法从DataReader获取数据。通用的方法是GetValue()和GetValues(),但需要额外的处理将值数据类型装换为引用数据类型,为了避免装箱,可以使用特性的如GetInt32()方法避免此问题。
·在DataSet中搜索数据。
在一个数据集中查询符合特定条件的行时,使用基于索引(index-based)的查看表将提高性能。给数据表指定主键(PrimaryKey)值时,就建立了一个索引。
·如果查询是在数据表的主键列上进行的,使用DataTable.Rows.Find代替DataTable.Select。
·查询非主键列,可以使用数据视图来提高多个数据查询的速度。当给数据视图添加排序时,将建立搜索时使用的索引,数据视图暴露了查询下层数据表的Find和FindRows方法。
·如果你不是查询表的排序视图,也可以通过为数据表建立数据视图获得基于索引的查看表的好处。注意如果你执行数据上的多个查询这是唯一的好处。如果你只执行单个查询,需要建立索引的过程将因为使用索引而降低了性能。
·如果需要多次执行SQL语句,可以使用cmd.Prepare()进行预编译,但如果只是执行一次SQL,则没有必要,因为没有预先编译的Command对象只需要一次网络往返,而编译的Command对象需要两次(一次往返用于解析和优化语句,一次往返用于执行语句并检索结果)。
· 当更新大量数据时,为了减少网络往返次数,可以使用参数数组或批处理取代预编译。
string sql = “INSERT INTO employees VALUES (? , ?)”; cmd.CommandText = sql; cmd.Prepare(); for(int i = 0 ; i<100 ; i++) { cmd.Parameters[0].Value = id[i]; cmd.Parameters[1].Value = name[i]; cmd.ExecuteNonQuery(); }
上面的代码演示了使用预编译的Command对象执行一条Insert语句多次的情况,这种情况下,为了执行100次插入操作,需要101次网络往返, 1次用于准备语句,另外100次用于执行迭代操作。
string sql = “INSERT INTO employees VALUES (? , ?)”; cmd.CommandText = sql; cmd.ArrayBindCount = 10; cmd.Prepare(); cmd.Parameters[0].Value = idArray; cmd.Parameters[1].Value = nameArray; cmd.ExecuteNonQuery();
将Command对象的CommandText属性设置为一个包含一条Insert语句和一个参数数组的字符串,这种方法只需要两次网络往返,一次用于准备语句,另外一次用于执行数组。
string sql = “INSERT INTO employees VALUES (? , ?)”+ “INSERT INTO employees VALUES (? , ?)”+ … “INSERT INTO employees VALUES (? , ?)” cmd.CommandText = sql; cmd.Prepare(); cmd.Parameters[0].Value = id[0]; cmd.Parameters[1].Value = name[0]; … cmd.ExecuteNonQuery();
以上代码使用批处理的方式。
如果应用程序更新断开连接的DataSet,并且数据提供程序支持批处理,可以通过设置DataAdapter对象的UpdateBatchSize属性优化性能。设置这个属性指定与数据库服务器的网络往返次数,从而优化性能:
SqlDataAdapter sda = new SqlDataAdapter(); sda.InsertCommand = cmd; sda.UpdateatchSize = 5; //告诉数据提供程序打包5条命令,并在一次网络往返中将它们发送到数据库