小觑数据库(SqlServer)查询语句执行过程

 近年来,越来越多的NoSql产品不断的以技术革命的者的身份跳出来:“你看哥是多么的快,你们关型型数据库真是战五渣阿”。是的,高性能的场景下NoSql真的很出彩。而我们关系型数据库只能在墙角哭泣"是的,没错,他们真的好快”。

  但是他们为啥哪么快?用了雷政富光环了吗?我们从了解数据库引挚执行过程来了解一些基础知识,看看我们关系型数据库到底慢在哪?我只粗懂一些SqlServer,只能从SqlServer讲起。但是SQLServer是一个非常复杂的软件。我们将通过一个查询的执行过程让你对SQlServer的核心引挚和运行过程做一个简单的了解。

  由于Select语句和Update语句基本一样,但是Update涉及到修改语句,所以我们直接从一条Sql语句开讲。update 码农表 set 女友=1 where 女友=0当我们码农写下这样一条Sql,小手轻按F5。 一个女朋友就产生了 一条语句在客户端被以TDS(Tabular Data Stream)协议形式中发给SqlServer服务端的网络接口(SQL Server Network Interface)。 什么是TDS协议这不重要。 因为TDS是一个专属协议,最早由Sysbase公司设计用于与数据库服务交互。你无须过多关心。SNI是一个 协议层用于在服务端和客户端建立网络连接,自SqlServer2005以后才有。它由一系列同时使用API组成。 
这些都不是我们要讲的重点,我们接着往下说。SNI收到TDS包解包后发现,哟,小子又提交Sql语句了。就将这个包标识为一个Sql命令,将此Sql命令发给命令解析器 command parser。 command Parser首先检查语法错误,如果发现语法错误,将错误通过协议层发回给客户端。如果语法验证通过,则会生成查询树。

查询树生成以后,SqlServer要进行一个非常消耗CPU的工作,就是根据查询树生成查询计划。SqlServer 并不是简单的将一个查询树翻译成查询计划,而是经过不断的比较和权衡。一条SqlServer可能有很多种查询计划.比如Select Name from table where a=1 如果这个表上没有任何索引,那么可能生成的查询计划就是表扫描。如果在A列上有索引,那么可能应用A列上的索引,也可能不用。总之,SqlServer比较各种方案找到一个开销最小的执行计划。而这个找到最小执行计划的过程,也是比较消耗CPU资源的。

为了更加有效的利用资源,SqlServer会对查询计划行缓存。将之前执行过的查询计划保存在内存中反复使用。 这里有个很有意思的性能优化点。因为内存对SqlServer来说也是非常重要的。如果缓存了大量的查询计划也会影响到SqlServer性能。那么不缓存费CPU,缓存费内存。我们该怎么办?我们应该更加高效的利用查询计划缓存。举个例子。刚才我们执行的语句update 码农表 set 女友=1 where 女友=0 然后又执行 update 码农表 set 女友=0 where 女友=1 对于SqlServer来说,它会把这当成二条完全不同的语句来处理。

这里有一个定义:以单独的SQL语句的形式执行的查询就是即席查询(Adhoc)。我们经常在程序里拼的sql,就是这种既席查询。

如果你的系统中有大量的既席查询,会产生大量查询计划缓存。所以推荐大家在程序中写Sql时,尽量的使用参数化。update 码农表 set 女友=1 where 女友=@女友数 既高效又防Sql注入,何乐而不为呢?

到上面为止,完成这些工作的组件都属于Sqlserver的关系引挚部分。关系查询可以说是SqlServer中最复杂的组件。用于确定查询的最佳执行方案。而市面上大多NoSql数据库没有关系引挚这部分。语句处理比较简单。 这就节约了一部分开销。

而数据的访问和管理则由存储引擎负责。事实上我也不算说存储引挚部分的细节。我们只谈谈关系型数据库和NoSql不太一样的部分。SqlServer和大多数NoSql数据库一样,都是非常依赖内存的。内存比磁盘快。这是人所共知的。 SqlServer也有将数据缓存中内存中的机制。每次查询请求数据时 SqlServer先在在内存中查询有没有对应的页。 这里的页是指一个段连续的8KB内存。这段内存是将数据库文件中的页(每个页8KB)直接映射到内存中的。如果发现有查询需要的页将直接将对应的页的内容打包成结果返回。如果没有,SqlServer则先去磁盘中找到对应的页将它载入内存。再将内存中的页返回。而查询结束,内存中的页也不在回收。将一直保存到内存中。直到SqlServer发现操作系统可用内存不足,才会将一些不常使用的内存页还给操作系统(多聪明)。而且SqlServer会预先分配一些空白的内存页。这样等到用时就不用现分配了。 知道了上面原理,你就能明白 为什么Page Life Expectancy (PLE) 性能记数器的数值越大越好了。Page Life Expectancy (PLE) 越大表示一个页面在内存在呆的时间越长。也就是说你的内存压力越小。

可是,如果要是更新时SqlServer怎么办?我们知道关系型数据库对持久性的要求比好多NoSql产品都有节操多了。那数据库是如何保证持久性(喂不要想太多)和尽量提高性能的呢? 
SqlServer使用了一种叫预写式日志的技术。简单来说就是你不是叫我干活吗?活还没干呢,我先把我要干的活写下来。然后等我有空时再干活。这么做有什么好处呢? 
  首先,我们知道磁盘的随机写入性能是很低的,相反,顺序写入性能要比随机写入高很多。如果每次用户更新数据时,都写入到数据库文件,那么很有可能产生一个随机读写。这样可能会影响性能。而SqlServer采用的是先写入日志。然后只更新内存中的对应的数据页。而日志一般都是顺序写入文件尾部的。这样一次随机的读写就被转换成一次顺序写加一次内存修改。性能自然有效提升。   而一般的NoSql数据库默认情况下并不保证写入的持久性。有的是定时刷到硬盘,但是并没有预写式日志,有的是先让你返回,成功不成功你再来问一次。

   那SqlServer又是如何保证内存中的数据被回写到硬盘呢?SqlServer和上面提到有些NoSqL有点类型。SqlServer有一个叫 Lazy Writer 线程,用于周期的检测 空闲缓存页的值,如果这个值较低,他将扫描正个数据缓存将较长时间没有没使用的页面过期。如果他发现一个很长时间没有被使用的脏页,他也会将被更的页面但还没有回写到硬盘的页–也叫脏页写入到磁盘,并将他在内存中标记为空闲页。 
   那如果SqlServer突然断电,而内存中的脏页并没有来得及写入到硬盘肿么办?SqlServer使用一种叫检查点的机制。 
   检查点进程确保任何和已提交事务相关的脏页能被写入到磁盘,也将未提交事务的脏页写入磁盘以确保效率。和Lazy Writer 不同,检点查并不将页面移出缓存。他只是将脏页刷入磁盘,然后页面头标识这个页面为 Clean Page。 默认情况下,在一个忙碌的服务器上,SQL Server 大约每一分钟发起一个检查点并记录在事务日志里。如果SqlServer 实现或数据库重新启动,那么恢复进程通过读取事务日志就知道那些工作是在检查点之前做的。检查点之前做的工作,他不用理会。注意:CheckPoint的触发条件,是在CheckPoint期间生成日志的大小。因此,大家见过内存中有很多脏页,却不引发CheckPoint的情况。 
   当SQL Server非正常原因关闭时,也就是在没有走CheckPoint(会在下面提到)时关闭了数据库,此时数据库中数据本身可能存在不一致的问题。因此在数据库再次启动的时候,会去扫描日志,找出那些未提交却写入持久化存储的数据,或已提交却未写入持久化存储的数据,来进行Undo和Redo来保证事务的一致性。SqlServer 会尝试保证数据库恢复时间小于1分钟,但是,至少需要有10M数据写入日志,sQLServer才会触发CheckPorint

到这,你也就明白了为什么数据库会慢于NoSql产品了。当然这也只是我所理解的一方面。可能还有很多地方NoSql有优化的地方。如果简化掉关系引挚,去掉预写式日志,数据库性能会不会也有质的飞跃呢?可这一切又值得吗? 
另外,如果你的数据文件和日志如果在一个磁盘上,那个可能这个预入式日志优势会大打折扣。原因你一定想的到。 
其实我只是借这NoSQL火抱一下大腿。这篇贴子并没有大多NoSql的内容。我对NoSql也不太了解。但是现在你不会NoSql你都当了意思和人家打招呼了。NoSql在一些对事务性要求不太高的地方大有用武之地。我打算看完硬盘里的波多老师作品就去学习Nosql 
了。

posted on 2014-04-14 17:01  Yunanw  阅读(1281)  评论(1编辑  收藏  举报