厚积薄发-SQLServer内核架构浅析
一:数据库的一些历史:
1.1992年,Oracle7发布。Oracle7有了存储过程、触发器、引用完整性校验、分布式事务处理
2.1994年,微软和Sybase合作分裂,微软开始研发自己的数据库。(Sybase是第一个运行于PC上的C/S数据库产品)
3.1995年,SQLSERVER6发布。SQLSERVER6是微软真正意义上的第一个数据库产品
4.1998年9月,Oracle发布了Oracle 8i. Oracle 8i支持用JAVA编写存储过程,支持XML,支持Linux。
5.1999年1月,SQLSERVER7正式发布. SQLSERVER第一次完整性的支持了行锁
SQLSERVER,作为一个数据库产品,比较重要的是两大块:存储引擎和查询引擎。其他的日志、事务、锁、索引等等基本上都是围绕他们来工作的。
一:查询引擎:
SQLSERVER是C/S产品,一条SQL语句要让SQLSERVER执行,必须要传输到SQLSERVER服务器端。为了保证客户端与服务端收发信息正常,保证数据能安全校验,保证数据接收的同步或异步性,需要在TCP/IP等等网络传输协议再构造一层协议。SQLSERVER引擎中用来接收来自客户端的SQL语句的是Open Data Services,它监听新的链接,清除失败的链接,将结果集,消息和状态等返回给客户端。SQLSERVER客户端与服务器之间传输的数据的格式被称为:tabular data stream。此数据流是通过ODBC、ADO或DB-Library来发送。这使得SQLSERVER对用户而言是透明的。 Open Data Services在监测客户端连接时,它能创建连接,如果服务完,它会将连接归入池中。链接在池中有一段生命期,它能自己释放连接。当有客户端连接中途突然断掉(如客户端重启了),它在侦听无回应后,能整理自己的连接。我们在SQLSERVER线程中看到的连接,就是Open Data Services创建的。
Open Data Services有了连接(可能是创建的可能是从池里拿出来的,池化、创建、销毁都是非常讲究技能的。池化多少,上下文资源如何保留,池化多长时间,什么时候该销毁,调度不当就会严重消耗资源),就把SQL接到Open Data Services的读缓冲区里面。SQLSERVER把检索到的数据,放进写缓冲区,写缓冲区一满就立即被Open Data Service发走。
SQLSERVER关系引擎一直在监听读缓冲区,一旦侦听到读缓充区的SQL语句,就会用命令分析器来构造查询树(即从SQL语法库中抽取SQLSERVER现有支持的各种语法和函数将你的SQL语句规范化,如SQL语句语法错误,则查询树无法构造)。成功构造查询树后,关系引擎使用命令优化器来将SQL语句生成执行和优化的方案。SQLSERVER会选择最节省内存、CPU利用率、I/O次数(I/O是性能优化最要命的地方)的那一种方案。而且优化器会根据查询树去选择合适的索引(如果使用索引代价大,它会自动选择全表扫描),也会根据查询树知道先取哪些表的数据,然后再内存中如何合并数据,以得到查询结果。
规范化、优化完SQL语句,查询执行器就会负责SQL的执行。因为SQL的执行要涉及到事务、锁、等待、CPU调度,内存页失效影响、I/O存取影响,所以查询执行器会协调很多其他模块来处理。
综上,查询引擎最主要是构造SQL查询树、优化裁剪SQL查询树,根据查询树产生执行计划,然后协调执行查询树,把结果返回去。
二:存储引擎:
访问方法管理器(access methods manager)会根据执行计划,命令“缓冲区管理器”来获取要存取哪些数据页和索引页。因为在硬盘上的数据是不可能计算处理的,必须要在内存中才能让CPU来计算。所以要存取那些数据页和索引页,就通知让缓冲区管理器来做。如果数据没有在内存中,就让缓冲区管理器来读入,如果数据已经在内存中了,缓冲区管理器只有返回即可。此过程对于访问方法管理器来说是透明的。
我们知道,数据页包含由行管理器来控制的数据行。索引页包含由索引管理器来负责的索引行。单行上的检索、修改、执行,又被事务管理器和锁管理器影响着。锁(有共享锁、排它锁、更新锁、意向锁。还分为行锁、页锁、表锁、数据库锁)的不同,又加上死锁的可能性,另加上事务(有显性事务和隐性事务两种)的影响,这个行是否能读、修改、读一致还是脏读,是等待事务和锁,还是可以进行,就受了很多影响。
因为一张数据页上放的行是有限的,尤其还有填充度的影响(如填充度为80%,就这个数据页面只能填充80%就必须分页,以防以后有数据插入的时候,就非常影响数据插页,这也是性能影响比较大,尤其在插入数据比较多的情况下)。SQLSERVER的一张数据页默认是64K,除去填充度和数据头,也没有多少可存储的数据了。这就是为了关系型数据库都劝阻大家要小表大数据。即:列要少,列要短,频繁访问的列要在前。数据可以海量。如果行长了,你想要检索和更新多少数据页,这需要多少页面调度,面临着页面失效和锁机制的影响。而且,大文本和可变行,都是指针存储,需要跳转查找,更浪费了不少时间。 而索引管理器,最主要在维护着索引B树。没有索引页,则要做全表扫描,要载入数据页,而且还要逐行扫描,如果遇上事务和更新锁,就更麻烦。所以,索引是非常重要的(一个表,可以建立多个索引,索引无须全表扫描就能直接找到所需要的行)。但是,若索引如果仅仅是男女,或者涉及到可变行,或者过多,都会造成维护索引页的成本和消耗非常多。索引页更要涉及到插页、拆页,频繁改动涉及到索引的字段,会让索引页剧烈变动,尤其数据量越大影响越大。
惰性写入器定期扫描老化数据,让硬盘和内存中的数据保持一致。有这个惰性写入器,就有了内存和硬盘的差异时间窗。就有可能出现异常。一旦服务器突然断电,没有来得及写会磁盘的时候就会涉及到另一个模块:日志管理器。日志管理器利用检查点的机制维护着日志文件。在服务器重新启动的时候,重写载入日志来把数据恢复到一致性。写日志,当然要比写数据要容易的多,快的多。因为写数据要操控内存和硬盘,还要注意权限、锁、事务,所以突然断电,你还没反应就来不及了。所以日志这种轻量级的方法,就可以在恢复一致性上有很好的帮助(当然,也丢失数据。日志页也没来得及写入硬盘)。
(事务管理器、锁管理器略、I/O管理器略)