性能问题是困扰数据库用户的常见问题之一。经常会有人因为遇到性能问题,质疑SQL Server处理大型数据应用的能力。其实,作为一个在市场上经营了二十多年,出了好几代版本的数据库产品,SQL Server作为一个企业级数据库的能力,是毋庸置疑的。在实际应用中,数据量达到几百GB,甚至上TB级,并发连接数超过1、2千个,每秒钟处理的请求 数量超过1000多个的SQL Server,现在已经很多了。在国内的一些大客户那里,我们也越来越多地看到这样的SQL Server。
那为什么有些SQL Server能跑得那么强劲,而很多用户的数据库还只有几十GB,就感觉跑不动了呢?在谈性能问题的常见原因之前,我们先谈谈SQL Server的几个和性能有关的重要特性。了解这些特性,对设计一个高效的数据库应用,是非常必要的。
1. SQL Server要访问的数据,是一定要缓冲在内存里的
不管是要查询的数据,还是要修改的数据,SQL Server在运行客户端发过来的语句,处理这些数据之前,都要检查其访问的数据是否在内存中。如果不在内存中,SQL Server会先把存储数据的页面从磁盘调入内存,然后再做真正的数据处理。
数据处理完毕后,SQL Server不会马上把这段数据缓存丢弃。只要SQL Server不缺内存,先前访问过数据页面就会一直缓存在SQL
Server进程的地址空间里。这样,如果下次有其他用户要访问同样的数据记录,SQL
Server就可以马上在内存中进行处理,而不需要再到磁盘上去找。
这样的设计,可以最大程度的重用内存,提高SQL Server的处理速度。同时也决定了,SQL Server是一个非常喜欢大内存的应用程序。
2. 如果SQL Server没有空闲的内存,而用户需要访问新的数据,SQL会把以前缓存的数据挑选一部分从内存中清除,腾出空间来缓存新的数据
SQL Server不会无限制地申请内存。它会根据用户的设置,以及系统的内存数量,计算自己的最大内存数。如果当前的内存大小以及达到了这个上限,SQL就不 会再向Windows申请更多的内存。这样的机理,可以保证SQL Server和Windows,以及运行在同一台机器上的其他应用程序和平共处。
当SQL Server的内存数已经达到最大值,内存空间已经缓存满各种各样的数据页面,而用户又要访问新的、还没有缓存在内存里的数据时,SQL
Server会根据现有数据访问的频度,把最老的、最不经常被人访问的数据,从内存里清除,从而腾出空间来缓存现在客户要访问的新数据。
所以最理想的状态,是用户要访问的数据永远都缓存在内存里,SQL Server从来都不需要到磁盘上去找。这也是数据库性能最佳的情况。这时候SQL Server几乎从来不需要做磁盘读。
如果经常发生用户要访问的数据不在内存里的情况,SQL Server就会被迫不停地在内存和磁盘之间倒腾数据,性能会受到严重的影响。而这时候,你会看到SQL在经常地作磁盘读的动作。
3. 表格中数据的组织与访问,和聚集索引的选择密切相关
SQL Server的一个显著的特点,是表格里数据的存储,是按照聚集索引所在字段的值排序的。而非聚集索引是建立在聚集索引结构之上的。如果一张表格没有聚集索引,数据是按照堆的方式存储,没有任何顺序。
对于同样的数据量,SQL Server对一个有聚集索引的表格的管理,远远比没有聚集索引的表格要有效。绝大多数情况下,一张大表如果想要有良好的性能,就必须有一个合适的聚集索引。没有聚集索引,只加非聚集索引,也不能够达到优化的性能。
这是SQL Server的一个很重要的特点。
4. 在缺省的事务隔离级别下,同一条记录上的读操作和写操作是互斥的
SQL Server实现的是ANSI 标准的四个隔离级别。在Read Committed这个缺省的隔离级别上,读操作会申请S锁,修改操作会申请X锁,S锁和X锁互斥。所以同一条记录上不能同时进行读操作和写操作。
业界的有些其他数据库产品,缺省使用行版本控制方式实现事务隔离,如果一个用户在修改某一条记录、但是没有提交事务,而另一个用户要读同一条记录,它会让第二个用户读到第一个人修改之前这条记录的值。所以读操作和写操作是可以同时进行的。
这种版本控制的隔离级别,并发度当然比SQL Server的要高,读写操作之间产生阻塞的几率要小。可是它的事务隔离效果和SQL是不同的。例如,对于某个银行账户的查询,假设账户里原来有1万元, 用户A开始一个事务,将账户里的1万元转出。在转出这个动作还没有完成时,用户B来查询账户余额。使用版本控制,用户B可以马上得到结果:1万元,但是这 个结果其实很有可能已经过时。使用SQL Server,用户B必须等到用户A转帐完成才能查到余额,但是他得到的一定是一个最新的值。
这两种隔离级别其实反映了两种用户需求,不能讲哪种好,哪种不好。喜欢版本控制这种隔离级别的用户,如果想要在SQL
Server里有同样的并发度,可以选择SQL里面的Snapshot Isolation Level(这个功能在SQL 2005里引入)。这样,SQL里的读写操作就不互斥了。
上面的这几个特征,决定了SQL Server的很多???为特点。如果跑在SQL Server上应用程序没有很好地按照上述的特征设计,就容易遇到各种各样的性能问题。
在现实应用中,SQL Server性能问题的常见原因有下面几点。
1. 索引的设计不够优化,从而迫使语句经常使用全表扫描的执行计划
如果没有好的索引帮助,SQL Server查询任何一条记录都有可能不得不把整张表都扫描一遍。这个在数据库比较小的时候影响不大,因为SQL能够把所有数据都缓存在内存里,就算是全 表扫描也不会太慢。随着数据量增大,内存会放不下。全表扫描的负担会越来越重,到最后会严重影响SQL
Server的整体性能。
这是一个很常见的SQL Server越跑越慢的原因。
2. 大的表格没有聚集索引,或者聚集索引建立在不合适的数据列上
对于没有聚集索引的表格,如果里面的记录数以万计或者更多,管理和查询都会增加很多开销。一个经验是,对于一个要经常使用的任何大表格,请建立聚集索引。索引所在的数据列,应该是一个或者几个重复性的记录不是很多的数据列。
业界的一些其他数据库可能对聚集索引这个概念不是非常强调,或者使用其他的方法管理表格。所以如果把非SQL
Server的数据库迁移到SQL Server里,一定要重新检查索引结构,按照SQL Server的特点,调整索引设置。否则迁移后在SQL Server上得不到好的性能,是很正常的。
3. 用户需要经常访问的数据量,远大于机器的内存数
前面我们已经说过,SQL
Server要操作的数据,是必须要缓存在内存里的。如果终端所有用户要经常访问的数据量的总和,远远超出SQL Server所拥有的内存数量,那SQL就会不得不在内存和磁盘间反复换页。这时候的SQL性能下降,可能就不是一两个数量级了。
用户需要经常访问的数据量的大小,通常跟以下几个因素有关。
a.用户发过来的语句定义
如果语句里有良好的条件约束,那数据量就能得到控制。如果条件约束性不强,那数据量就会随着表格里的记 录数增长而增长。一个例子是,用户总是查询“今年的某某数据”。那在一年开始的时候,查询肯定很快。到年底时候的数据量可是一月份的12倍。那时候访问的 数据量也很可能就是12倍。
b.SQL Server是否能够利用索引,使用Seek的方式找到数据,而不是扫描全表
如果没有好的索引,哪怕用户要访问的数据只是表格里的很小一部分,SQL可能也需要遍历整个表格。在这种情况下,也会出现表格越大,SQL跑得越慢的情况。如果索引设计地比较优化,SQL Server的数据访问量,不应该跟表格的大小有着绝对的关系。
c.应用程序的性质
数据库应用基本可以分两大类:OLTP类型的,和Data Warehouse类型的。前者会处理大量的小事务,比如病人挂号、超市结帐、仓库进货等。这种应用用户发过来的请求不会很复杂,每次要处理的数据量也比 较小,但是要求的响应速度要很快。有可能0.5秒的等待都会带来性能问题。所以对这种应用,其访问的数据应该保证总是缓存在内存里。
Data Warehouse类型的应用主要是做数据分析和整理,经常是为了产生一些报表。这种应用主要以查询为主,肯定会访问大量的数据,会出现数据库越大,访问的数据量越多的情况。但是客户端可以容忍一定时间的等待。对于这种应用,其访问的数据不在内存里关系也不大。
对于OLTP类型的应用,应该尽量保证用户经常要访问的数据能够长时间地缓存在内存里。所以在设计应用逻辑和数据库时,就要有所考虑,控制用户访问 的数据量,建立有效的索引,避免全表扫描。在管理上,也要有历史数据归档的机制,控制整个数据库的大小。如果证明用户需要访问的数据量就是比内存大,升级 内存也是需要考虑的方案。
对于报表类型的应用,因为访问的数据量会很大,磁盘换页是难以避免的。当然良好的索引设计,对性能也会很有帮助。
要避免的情况,是同一个SQL Server既在运行OLTP类型的应用,又时不时地在运行报表类型的应用。它们两者会产生很大的相互干扰,对OLTP应用的响应速度会产生破坏性的影响,而且很难用调整数据库设计,或者升级硬件的方法轻易解决。
4. 数据库应用是从其他非SQL Server的架构下直接移植过来的
前面已经谈过,SQL Server缺省的事务隔离级别是Read Committed,和其他数据库可能不一样。另外,很多应用是通过调用数据库控件的接口来访问数据库的,而不是直接调用SQL语句或者存储过程。要达到 同一个目的,程序里有很多种实现的方法。有些方法可能在其他数据库上性能会不错,但是到SQL
Server上有可能就要换一个做法,或者换一种驱动。再加上不同的数据库系统对指令的写法可能有不同的建议,表格上的索引要求也不一样。所以要在SQL
Server上实现良好的性能,就必须对迁移过来的应用在SQL Server平台上进行优化。没有测试、优化而直接移植过来的应用,常常不能发挥SQL Server的长处,性能欠佳。