SQL Server内核架构剖析 (转载)
本文原贴出自:http://www.sqlserver.com.cn
这篇文章在我电脑里好长时间了,今天不小心给翻出来了,觉得写得很不错,因此贴出来共享。
不得不承认的是,一个优秀的软件是一步一步脚踏实地积累起来的,众多优秀的程序员呕心沥血,他们已经不是在简单的写代码,而是在创作一门艺术。
和前面提到的暴雪公司的发展相比他们有一个相同之处,即:他们只做经典。不能说他们集中的全世界最优秀的程序员,而实际上他们集中的是全世界最好的思想,并且付诸实践。
成功不是靠急于求成,而是靠远见。祝Microsoft SQL Server越走越远。
我们做管理软件的,主要核心就在数据存储管理上。所以数据库设计是我们的重中之重。为了让我们的管理软件能够稳定、可扩展、性能优秀、可跟踪排错、可升级部署、可插件运行,
我们往往研发自己的管理软件开发平台。我们总是希望去学习别人的开发平台(如用友或金蝶或SAP),但我们却总是感叹管理软件业务处理细节繁多,
而数据库管理软件却简单的SELECT、INSERT、DELETE、UPDATE四个命令就搞定。
我们多希望有一天能做出一个架构,也可以这么简单就搞定管理软件。我们往往研究别人的架构,却忘记了我们身边我们最熟悉的数据库的架构。
所以,今天,我想带领大家一起剖析一下数据库的架构,来探索数据库的架构思想。而我本人呢,只熟悉SQLSERVER这一种数据库产品,所以我就拿SQLSERVER来分析。
在讲SQLSERVER内部原理的之前,我觉得非常有必要向大家介绍一下SQLSERVER的历史。
让我们站在1999年,看看计算机数据库业界到底处于什么状态。
1999年,Oracle已经于1998年9月发布了Oracle 8i(可能中文版在1999年才来到中国)。
Oracle 8i支持用JAVA编写存储过程,支持XML,支持Linux。
1999年1月,SQLSERVER7正式发布。SQLSERVER7重构了整个数据库引擎(相当于重写了SQLSERVER)。
SQLSERVER第一次完整性的支持了行锁(有没有搞错,过去人是怎么使用数据库产品的。1988年,Oracle6就支持行锁。另外1988年,Oracle就开始研发ERP产品。谁说Oracle是ERP门外汉,可以参考这个)。
看看他们俩的前一个版本。如果你入行比较晚(2000年以后),可能对以下文字更感到惊讶。
1992年,Oracle7发布。有了存储过程、触发器、引用完整性校验、分布式事务处理。(天哪,Oracle7才有了这些东西)。
1995年,SQLSERVER6发布。SQLSERVER6是微软真正意义上的第一个数据库产品(真是爆料,大家没想到SQLSERVER6才是微软第一个数据库产品,那版本6之前的5、4、3、2、1是怎么度过的)。
因为1994年,微软和Sybase掰了(Sybase是第一个运行于PC上的C/S数据库产品)。微软为了进入数据库产品领域,自己又没有经验,
于是和Sybase一起合作(当时微软是全世界第一大软件公司,微软1986年上市。Sybase有产品,缺钱。微软缺产品,有钱。于是一拍即合)。
直到1994年,微软也不需要Sybase了(已经学会了数据库技术),Sybase也感觉微软太狼子野心,于是合作分裂。微软开始自己做自己的数据库。
历史说完。我们言归正传。
很多入门级做管理软件的,SQL语句玩的熟练,从子查询到Having到交叉表统计SQL都能做出来,甚至存储过程能写2000多行,游标、自定义函数、触发器、约束用的眼花缭乱。
再入点门,在SQL查询器中可以使用SQL分析优化索引,用SQL Profile可以跟踪SQL,甚至在性能查看器中监测SQLSERVER内存、CPU、线程、I/O的运行状态,甚至为自己会使用DBCC而沾沾自喜。
你是如此熟悉SQLSERVER,又是对SQLSERVER如此陌生。
我今天就用架构的角度来给大家分析一下SQLSERVER架构和原理。短短一篇博文肯定只能面上的多一些,深一层的可能需要连载数篇文章甚至一块大砖头书才能讲完整。
不过,我希望我的博文能够抛砖引玉,使大家能从一个过去没有想过的角度去看SQLSERVER。
SQLSERVER,作为一个数据库产品,我个人认为,最重要的就是两大块:存储引擎和查询引擎(关系引擎)。
其他的日志、事务、锁、索引等等都是围绕他们来工作的。
关系引擎(查询引擎)上层
SQLSERVER是C/S产品,所以一条SQL语句要让SQLSERVER执行,必须要传输到SQLSERVER服务器端。传输,我们当然知道需要NetBEUI、TCP/IP等等网络传输协议。但是光有这些还不行。
客户端如何发,服务器端如何收,如何确认发的和收的正确完整,如何确实发的和收的已经结束,如何发和收能跨越各种网络协议(如UNIX和WINDOWS和NOVELL通讯),如何保证数据安全校验,
如何保证数据收发是同步还是异步,就需要在网络传输协议之上再构造一层协议。SQLSERVER既支持IPC机制(interior process call),也支持RPC机制(remote process call)。你想想你的管理软件开发平台是否有这一层。
当然,现在的消息服务器已经专业的提供了这一机理,可靠的、安全的、高效的、异步的、消息压缩、消息拆分、智能路由、集群,跨越不同的操作系统、不同的编程语言、不同的通讯协议、不同的硬件平台的消息数据传输。
可能你过去不了解消息中间件,通过这一案例可以知道消息中间件的用途。
SQL语句被可靠无误的发送到了服务器端,SQLSERVER引擎中第一个模块就来接待这个SQL数据。这个模块的名字叫:Open Data Services。
它监听新的连接;清除失败连接;将结果集、消息和状态返回给客户端。
SQLSERVER客户端和服务器端之间传输数据,数据包是有格式的。在SQLSERVER中被称为tabular data stream。
这个数据流是令牌控制客户端和服务器端对话(否则,客户端说了N句话,服务器端返回N句话,没有令牌就混在一起了,不知道哪个回答是对应哪个请求的)。
我们往往不能直接和Open Data Services打交道,把数据放进来。而是我们必须通过ODBC、ADO或DB-Library来发送tabular data stream。
而SQLSERVER返回的数据结果,也是通过这些ODBC之类发回tabular data stream。你看看SQLSERVER设计的多巧妙,一个通用数据访问接口屏蔽了你和SQLSERVER之间直接沟通,
就如同WINDOWS API屏蔽了内核让你无法访问,就如同DirectX屏蔽了UI和外设的操控。
SQL语句-ODBC-编码成tabular data stream-IPC或RPC-网络协议-IPC或RPC-解码tabular data stream-ODBC-Open Data Services。
Open Data Services监测客户端连接。如果并发太多,它会创建连接,如果服务完,它会自己维护连接归入池中。在池中保留一段生命期,它会自己释放连接。
如果有的客户端连接中途突然断掉(如客户端重启了),它在侦听后无回应发送keepalive数据包侦测是否死掉,它也会自己整理自己的连接的。
我们在SQLSERVER线程中看到的连接,就是Open Data Services创建的。
Open Data Services有了连接(可能是创建的可能是从池里拿出来的,池化、创建、销毁都是非常讲究技能的。
池化多少,上下文资源如何保留,池化多长时间,什么时候该销毁,调度不当就会严重消耗资源),就把SQL hang住。这时,是接到了Open Data Services的读缓冲区里面。
这个缓冲区为高性能处理数据的SQLSERVER带来一丝喘息机会,而就这一丝喘息机会,让SQLSERVER可以游刃有余(你的设计有吗?)。
而Open Data Services有一个写缓冲区。SQLSERVER把检索到的数据,检索出来就立即放进写缓冲区,写缓冲区一满就立即被Open Data Service发走。
当我过去研究SQLSERVER原理的时候,我常常赞叹,一个小小的SQLSERVER外围模块都设计如此精妙,实在让人佩服。我们经常在追求海量数据存储和Cache架构,我们却无视我们手边的SQLSERVER。
SQL语句放到读缓冲区,SQLSERVER的查询引擎(关系引擎)就开始工作了。它总是在侦听这个读缓冲区。
SQL语句遇到的关系引擎的第一个模块就是命令分析器。我们在SQL查询分析器中看到的查询分析结果就是它的输出杰作。它来构造查询树。
首先是将你的SQL语句规范化(你想想你写的软件代码,输入数据来了什么都不管就直接处理,连输入数据校验都没有,怎能稳定),否则以后的步骤将不好操作,如果你的SQL语句有语法错误,
这个查询树的构造就无法完成,于是中断。而要规范一个SQL语句,首先要从SQL语法库中抽取SQLSERVER现有支持的各种语法和函数,SELECT * 平展 ,字段访问权限校验。
一旦构造成功,关系引擎的第二个模块就是命令优化器,来裁剪这棵树。一个SQL语句可以生成多种执行和优化的方案(如果你使用过那种SQL优化工具的话,你就能理解),
SQLSERVER会选择最节省内存、CPU利用率、I/O次数(I/O是性能优化最要命的地方,往往性能就瓶颈在I/O上)的那一种方案。
优化器会根据每张表的数据统计/统计信息(有时候你为了性能优化,必须定时期同步更新一下统计,否则优化就会有误差)。
而且优化器也会根据查询树去选择合适的索引(如果使用索引代价大,它会自动选择全表扫描),优化器也会根据查询树知道先取哪些表的数据,
然后再内存中如何合并数据,以得到你想要的结果(有时候想想优化器真伟大,你一个SQL过去,它需要在极短的时间内做多少事啊,为了能在极短时间内确定一个相对优化的方案,它也不可能穷举所有可能的方案,所以我们做海量数据优化的时候,往往评估多种方案,然后修改自己的SQL语句以符合产生最优的方案)。
规范化、优化完SQL语句,就要产生执行计划了。SQL管理器负责执行计划的产生。因为你发过来的SQL语句可能是一个SELECT,也可能是一个INSERT或UPDATE。即使SELECT,
也面临着用户权限的限制(你如果设置过某一个SQLSERVER用户的对象权限和列权限,你就会明白)。而INSERT之类更新语句,又会涉及到权限、默认值、约束、表达式、主外键、触发器。
一个优化完的SQL,具体要真正让SQLSERVER从内存或硬盘上把数据找出来或者更新回去,需要很多细节的步骤。
查询执行器来负责SQL的执行。因为SQL的执行要涉及到事务、锁、等待、CPU调度,内存页失效影响、I/O存取影响,所以查询执行器会协调很多其他模块,但各个模块来负责处理,
而查询执行器并不真正全部包办,否则让事务管理器、锁管理器、索引管理器、页面文件管理器、缓冲管理器、行管理器、日志管理器干吗去。
查询执行器是查询引擎的最后一个模块,接下来的模块都属于存储引擎的范畴。所以,从上看,查询引擎最主要是构造SQL查询树、优化裁剪SQL查询树,根据查询树产生执行计划,然后协调执行查询树,
把结果返回去。
存储引擎 下层
而真正要把数据取出来或存进去,就需要存储引擎来工作了。
首先根据执行计划,要存取哪些数据页和索引页。这就是访问方法管理器(access methods manager)要做的事情。但其实真要打开这些页,还不是访问方法管理器自己要亲手干的。
亲手干这个活的是一个叫“缓冲区管理器”的模块。因为在硬盘上的数据是不可能计算处理的,必须要在内存中才能让CPU来计算。所以要存取那些数据页和索引页,就通知让缓冲区管理器来做。
如果数据没有在内存中,就让缓冲区管理器来读入,如果数据已经在内存中了,缓冲区管理器只有返回即可。这个过程是被缓冲区管理器来屏蔽的,对于访问方法管理器是透明的。
大家可不要以为访问方法管理器啥事不做,只是一个发布调度命令的。这可错怪了它。因为SQLSERVER要保证高速处理,必须预先预测好哪些数据页和索引页要处理。
不能人家缓冲管理器已经处理完,你访问方法管理器才计算下一步将要处理的页面。要知道,这些管理器可是不分哪个用户来处理的。如果接受来自100多个并发的用户,发来各种各样的数据处理请求,
你怎么能预测到哪些数据页和索引页要处理呢?这就需要一个统一的调度。而且这个统一的调度也影响着缓冲区管理器。你不能请求一个大数据,缓冲区管理器这才火烧屁股才扩大缓冲区,然后装载数据,
那样流水线就停下了。缓冲区管理器必须预先知道将在不久要有一个大数据,所以在并行运算的时候就有独立线程来扩展了缓冲区。因为扩大缓冲区还和操作系统有关。
你要扩大缓冲区,正好遇到WINDOWS页面失效(缺页中断 page fault),就涉及到你的虚拟文件的变化。而页面失效又会影响CPU和I/O。所以页面失效是一个性能影响很大的问题。
而提高命中率是我们性能优化一直努力的重点。如果数据长时间不用,缓冲区管理器就要让这块内存数据过期,可以被新的数据覆盖。否则缓冲区老加载不卸载也不行。
再说,有些数据已经被更新了,你数据老化了,不重新读入,你的数据就引起读错误了。
我们知道,数据页包含数据行。索引页包含索引行。数据行就由行管理器来控制。而索引行,由索引管理器来负责。
而单行上的检索、修改、执行,又被事务管理器和锁管理器影响着。事务,有显性事务和隐性事务两种。而锁,又有共享锁、排它锁、更新锁、意向锁。而锁,还分为行锁、页锁、表锁、数据库锁。
而锁,又有死锁的可能性。锁的不同,加上事务的影响,这个行是否能读、能修改,能怎样的读(读一致还是脏读),是等待事务和锁,还是可以进行,就受了很多影响。
因为一张数据页上放的行是有限的,尤其还有填充度的影响(如填充度为80%,就这个数据页面只能填充80%就必须分页,以防以后有数据插入的时候,就非常影响数据插页,
这也是性能影响比较大,尤其在插入数据比较多的情况下)。SQLSERVER的一张数据页默认是8K,除去填充度和数据头,也没有多少可存储的数据了。
这就是为了关系型数据库都劝阻大家要小表大数据。也就是说,列要少,列要短,频繁访问的列要在前。数据可以海量。如果行长了,你想要检索和更新多少数据页,这需要多少页面调度,
面临着页面失效和锁机制的影响。而且,大文本和可变行,都是指针存储,需要跳转查找,更浪费了不少时间。
而索引管理器,最主要在维护着索引B树。没有索引页,我们就要做全表扫描了,那需要载入多少数据页,而且还要逐行扫描,如果遇上事务和更新锁,就更有问题。
所以,索引是非常重要的。而一个表,可以建立很多索引。索引,能直接找到所需要的行,而无须全表扫描。但是,你的索引如果仅仅是男女,或者你的索引涉及到可变行,都对索引不利。
索引,不宜建立多。否则维护索引页的成本和消耗也非常多。索引页更要涉及到插页、拆页,频繁改动涉及到索引的字段,会让索引页剧烈变动,尤其数据量越大影响越大。
我就不在这里讲解如何利用索引优化SQL了,否则一本书也讲不完。
数据不断存取,数据不断被维护,载入内存或从内存中写入硬盘。其实都是惰性写入器在照顾。惰性写入器来定期扫描老化数据,让硬盘和内存中的数据是一致的。
有这个惰性写入器,就有了内存和硬盘的差异时间窗。就有可能出现异常。一旦服务器突然断电,没有来得及写会磁盘的怎么办。也也涉及到另一个模块:日志管理器。
日志管理器利用检查点的机制维护着日志文件。在服务器重新启动的时候,重写载入日志来把数据恢复到一致性。写日志,当然要比写数据要容易的多,快的多。
因为写数据要操控内存和硬盘,还要注意权限、锁、事务,所以突然断电,你还没反应就来不及了。所以日志这种轻量级的方法,
就可以在恢复一致性上有很好的帮助(当然,也丢失数据。日志页也没来得及写入硬盘)。
讲到这里,就剩下事务管理器、锁管理器。这两个管理器和显性事务、隐性事务、显性锁、隐性锁、事务隔离级别、锁级别、行管理器、索引管理器都有很多关系。
微软有WINDOWS优势,又有Jim Gray这样的巨师坐镇(Jim Gray是图灵奖获得者,就是此爷提出了数据库事务这一概念。盖茨为了让此爷为微软工作,而此爷不喜欢雷德蒙天天下雨的天气,
于是在加州阳光中给此爷单独建了一座研究院 微软加州研究院)。
百度百科:http://baike.baidu.com/link?url=n0vbmC2FrrVB2xWyHFlQqntBeG9yOvX1zqrHYM6J9R0-xFAkjbnTuSh6Vh94c_m2yQ8nRdZAqD3V9llgVLVwxa
所以,在性能上,我个人认为SQLSERVER的性能是非常优秀的(你想想,一个数据库产品的性能受什么方面的影响)。
至于业界老称SQLSERVER无法管理海量数据,性能不佳,我个人感觉都是业界在以讹传讹。而尤其中国内地IT业界,大部分都是入门级在跟帖嘈杂,尤其还有一批更不懂技术的媒体记者或写手。
如果真要去说SQLSERVER不行,大型海量数据管理必须用某某数据库产品,我建议从内部原理、内部架构、内部实现三个层次诸多方面来剖析到底在不在理。
最后就是I/O管理器了。我一直不认同SQLSERVER内核中有I/O管理器。因为SQLSERVER使用的是和WINDOWS同样的页面调度和页面分配方法。
何必要自己另创一套呢。就如同SQLSERVER把页面、硬盘、内存、线程、CPU交给了WINDOWS一样。
SQLSERVER作为WINDOWS上的一个应用软件,应该和WINDOWS上的其他软件一样被WINDOWS管理。SQLSERVER又不跨平台,无须自己管理。
除了SQLSERVER这些内核涉及精妙以外,SQLSERVER的外围工具也设计的相当好。
如SQLSERVER的用户安全性管理方法、对象分类(表、列、约束、默认、索引、触发器、存储过程、视图、主键)、对象权限方法、元数据自管理方法、SQL语言、SQL查询分析器、SQL跟踪器、SQL性能分析器、SQL数据库(master\msdb\tempdb\model)。
想一想,你的管理软件平台有这些架构思想吗?
http://blog.csdn.net/hzbooks/article/details/2247912
Edgar Frank Codd (1981) -- 关系数据模型
Jim Gray(1998) -- 数据库和事务处理
2015-4-13 补充
Well I don't know whether writer wrote about Log manger or not but I would try to explain you
Storage engine of SQL Server has component called as Transaction Manager which manages locks for transaction an also manages transaction logs.
This Transaction manager has two components
1. Lock Manager
2. Log Manager