关系型数据库进阶(二)全局概览

  上篇文章 关系型数据库进阶(一)数据库基础,我们已经了解了数据库基础,现在我们需要回来看看数据库的全貌了。

  数据库是一个易于访问和修改的信息集合。不过简单的一堆文件也能达到这个效果。事实上,像SQLite这样最简单的数据库也只是一堆文件而已,但SQLite是精心设计的一堆文件,因为它允许你:

1  使用事务来确保数据的安全和一致性

2  快速处理百万条以上的数据

3  数据库一般可以用如下图形来理解:

本篇文章,我不会特别关注如何组织数据库或者如何命名各种进程,因为我选择了自己的方式来描述这些概念以适应本文。区别就是不同的组件,总体思路为:数据库是由多种互相交互的组件构成的。

核心组件:

进程管理器(process manager):很多数据库具备一个需要妥善管理的进程/线程池。再者,为了实现纳秒级操作,一些现代数据库使用自己的线程而不是操作系统线程。

网络管理器(network manager):网路I/O是个大问题,尤其是对于分布式数据库。所以一些数据库具备自己的网络管理器。

文件系统管理器(File system manager):磁盘I/O是数据库的首要瓶颈。具备一个文件系统管理器来完美地处理OS文件系统甚至取代OS文件系统,是非常重要的。

内存管理器(memory manager):为了避免磁盘I/O带来的性能损失,需要大量的内存。但是如果你要处理大容量内存你需要高效的内存管理器,尤其是你有很多查询同时使用内存的时候。

安全管理器(Security Manager):用于对用户的验证和授权。

客户端管理器(Client manager):用于管理客户端连接。

……

工具:

备份管理器(Backup manager):用于保存和恢复数据。

复原管理器(Recovery manager):用于崩溃后重启数据库到一个一致状态。

监控管理器(Monitor manager):用于记录数据库活动信息和提供监控数据库的工具。

Administration管理器(Administration manager):用于保存元数据(比如表的名称和结构),提供管理数据库、模式、表空间的工具。【译者注:好吧,我真的不知道Administration manager该翻译成什么,有知道的麻烦告知,不胜感激……】

……

查询管理器:

查询解析器(Query parser):用于检查查询是否合法

查询重写器(Query rewriter):用于预优化查询

查询优化器(Query optimizer):用于优化查询

查询执行器(Query executor):用于编译和执行查询

数据管理器:

事务管理器(Transaction manager):用于处理事务

缓存管理器(Cache manager):数据被使用之前置于内存,或者数据写入磁盘之前置于内存

数据访问管理器(Data access manager):访问磁盘中的数据

在本文剩余部分,我会集中探讨数据库如何通过如下进程管理SQL查询的:

客户端管理器

查询管理器

数据管理器(含复原管理器)

客户端管理器

客户端管理器是处理客户端通信的。客户端可以是一个(网站)服务器或者一个最终用户或最终应用。客户端管理器通过一系列知名的API(JDBC, ODBC, OLE-DB …)提供不同的方式来访问数据库。

客户端管理器也提供专有的数据库访问API。

当你连接到数据库时:

1  管理器首先检查你的验证信息(用户名和密码),然后检查你是否有访问数据库的授权。这些权限由DBA分配。

2  然后,管理器检查是否有空闲进程(或线程)来处理你对查询。

3  管理器还会检查数据库是否负载很重。

4  管理器可能会等待一会儿来获取需要的资源。如果等待时间达到超时时间,它会关闭连接并给出一个可读的错误信息。

5  然后管理器会把你的查询送给查询管理器来处理。

6  因为查询处理进程不是『不全则无』的,一旦它从查询管理器得到数据,它会把部分结果保存到一个缓冲区并且开始给你发送。

7  如果遇到问题,管理器关闭连接,向你发送可读的解释信息,然后释放资源。

查询管理器

这部分是数据库的威力所在,在这部分里,一个写得糟糕的查询可以转换成一个快速执行的代码,代码执行的结果被送到客户端管理器。这个多步骤操作过程如下:

1 查询首先被解析并判断是否合法

2 然后被重写,去除了无用的操作并且加入预优化部分

3 接着被优化以便提升性能,并被转换为可执行代码和数据访问计划。

4 然后计划被编译

5 最后,被执行

这里我不会过多探讨最后两步,因为它们不太重要。

查询解析器

每一条SQL语句都要送到解析器来检查语法,如果你的查询有错,解析器将拒绝该查询。比如,如果你写成”SLECT …” 而不是 “SELECT …”,那就没有下文了。

但这还不算完,解析器还会检查关键字是否使用正确的顺序,比如 WHERE 写在 SELECT 之前会被拒绝。

然后,解析器要分析查询中的表和字段,使用数据库元数据来检查:

1 表是否存在

2 表的字段是否存在

3 对某类型字段的运算是可能(比如,你不能将整数和字符串进行比较,你不能对一个整数使用 substring() 函数)

接着,解析器检查在查询中你是否有权限来读取(或写入)表。再强调一次:这些权限由DBA分配。

在解析过程中,SQL 查询被转换为内部表示(通常是一个树)。

如果一切正常,内部表示被送到查询重写器。

查询重写器

在这一步,我们已经有了查询的内部表示,重写器的目标是:

1 预优化查询

2 避免不必要的运算

3 帮助优化器找到合理的最佳解决方案

重写器按照一系列已知的规则对查询执行检测。如果查询匹配一种模式的规则,查询就会按照这条规则来重写。下面是(可选)规则的非详尽的列表:

视图合并:如果你在查询中使用视图,视图就会转换为它的 SQL 代码。

子查询扁平化:子查询是很难优化的,因此重写器会尝试移除子查询

例如:

SELECT PERSON.* FROM PERSON
WHERE PERSON.person_key IN
(SELECT MAILS.person_key FROM MAILS
WHERE MAILS.mail LIKE ‘christophe%‘);

会转换为:

SELECT PERSON.*

FROM PERSON, MAILS

WHERE PERSON.person_key = MAILS.person_key

and MAILS.mail LIKE ‘christophe%‘;

1 去除不必要的运算符:比如,如果你用了 DISTINCT,而其实你有 UNIQUE 约束(这本身就防止了数据出现重复),那么 DISTINCT 关键字就被去掉了。

2 排除冗余的联接:如果相同的 JOIN 条件出现两次,比如隐藏在视图中的 JOIN 条件,或者由于传递性产生的无用 JOIN,都会被消除。

3 常数计算赋值:如果你的查询需要计算,那么在重写过程中计算会执行一次。比如 WHERE AGE > 10+2 会转换为 WHERE AGE > 12 , TODATE(“日期字符串”) 会转换为 datetime 格式的日期值。

4 (高级)分区裁剪(Partition Pruning):如果你用了分区表,重写器能够找到需要使用的分区。

5 (高级)物化视图重写(Materialized view rewrite):如果你有个物化视图匹配查询谓词的一个子集,重写器将检查视图是否最新并修改查询,令查询使用物化视图而不是原始表。

6 (高级)自定义规则:如果你有自定义规则来修改查询(就像 Oracle policy),重写器就会执行这些规则。

7 (高级)OLAP转换:分析/加窗 函数,星形联接,ROLLUP 函数……都会发生转换(但我不确定这是由重写器还是优化器来完成,因为两个进程联系很紧,必须看是什么数据库)。

物化视图 。谓词,predicate,条件表达式的求值返回真或假的过程】

重写后的查询接着送到优化器,这时候好玩的就开始了。

统计

研究数据库如何优化查询之前我们需要谈谈统计,因为没有统计的数据库是愚蠢的。除非你明确指示,数据库是不会分析自己的数据的。没有分析会导致数据库做出(非常)糟糕的假设。

但是,数据库需要什么类型的信息呢?

我必须(简要地)谈谈数据库和操作系统如何保存数据。两者使用的最小单位叫做页或块(默认 4 或 8 KB)。这就是说如果你仅需要 1KB,也会占用一个页。要是页的大小为 8KB,你就浪费了 7KB。

回来继续讲统计!当你要求数据库收集统计信息,数据库会计算下列值:

1表中行和页的数量

2表中每个列中的:

唯一值
数据长度(最小,最大,平均)
数据范围(最小,最大,平均)

3表的索引信息

这些统计信息会帮助优化器估计查询所需的磁盘 I/O、CPU、和内存使用

对每个列的统计非常重要。
比如,如果一个表 PERSON 需要联接 2 个列: LAST_NAME, FIRST_NAME。
根据统计信息,数据库知道FIRST_NAME只有 1,000 个不同的值,LAST_NAME 有 1,000,000 个不同的值。
因此,数据库就会按照 LAST_NAME, FIRST_NAME 联接。
因为
LAST_NAME 不大可能重复,多数情况下比较 LAST_NAME 的头 2 、 3 个字符就够了,这将大大减少比较的次数。

不过,这些只是基本的统计。你可以让数据库做一种高级统计,叫直方图。直方图是列值分布情况的统计信息。例如:

出现最频繁的值

分位数

  这些额外的统计会帮助数据库找到更佳的查询计划,尤其是对于等式谓词(例如: WHERE AGE = 18 )或范围谓词(例如: WHERE AGE > 10
and AGE < 40),因为数据库可以更好的了解这些谓词相关的数字类型数据行(注:这个概念的技术名称叫选择率)。

统计信息保存在数据库元数据内,例如(非分区)表的统计信息位置:

Oracle: USER / ALL
/ DBA_TABLES 和 USER / ALL / DBA_TAB_COLUMNS

DB2: SYSCAT.TABLES 和 SYSCAT.COLUMNS

  统计信息必须及时更新。如果一个表有 1,000,000 行而数据库认为它只有 500 行,没有比这更糟糕的了。统计唯一的不利之处是需要时间来计算,这就是为什么数据库大多默认情况下不会自动计算统计信息。数据达到百万级时统计会变得困难,这时候,你可以选择仅做基本统计或者在一个数据库样本上执行统计。

  举个例子,我参与的一个项目需要处理每表上亿条数据的库,我选择只统计10%,结果造成了巨大的时间消耗。本例证明这是个糟糕的决定,因为有时候 Oracle 10G 从特定表的特定列中选出的 10% 跟全部 100% 有很大不同(对于拥有一亿行数据的表,这种情况极少发生)。这次错误的统计导致了一个本应 30 秒完成的查询最后执行了 8 个小时,查找这个现象根源的过程简直是个噩梦。这个例子显示了统计的重要性。

注:当然了,每个数据库还有其特定的更高级的统计。如果你想了解更多信息,读读数据库的文档。话虽然这么说,我已经尽力理解统计是如何使用的了,而且我找到的最好的官方文档来自PostgreSQL

查询优化器

所有的现代数据库都在用基于成本的优化(即CBO)来优化查询。道理是针对每个运算设置一个成本,通过应用成本最低廉的一系列运算,来找到最佳的降低查询成本的方法。

为了理解成本优化器的原理,我觉得最好用个例子来『感受』一下这个任务背后的复杂性。这里我将给出联接 2 个表的 3 个方法,我们很快就能看到即便一个简单的联接查询对于优化器来说都是个噩梦。之后,我们会了解真正的优化器是怎么做的。

对于这些联接操作,我会专注于它们的时间复杂度,但是,数据库优化器计算的是它们的 CPU 成本、磁盘 I/O 成本、和内存需求。时间复杂度和 CPU 成本的区别是,时间成本是个近似值(给我这样的懒家伙准备的)。而 CPU 成本,我这里包括了所有的运算,比如:加法、条件判断、乘法、迭代……还有呢:

每一个高级代码运算都要特定数量的低级 CPU 运算。

对于 Intel Core i7、Intel Pentium 4、AMD Opteron…等,(就 CPU 周期而言)CPU 的运算成本是不同的,也就是说它取决于 CPU 的架构。

使用时间复杂度就容易多了(至少对我来说),用它我也能了解到 CBO 的概念。由于磁盘 I/O 是个重要的概念,我偶尔也会提到它。请牢记,大多数时候瓶颈在于磁盘 I/O 而不是 CPU 使用。

索引

在研究 B+树的时候我们谈到了索引,要记住一点,索引都是已经排了序的。

仅供参考:还有其他类型的索引,比如位图索引,在 CPU、磁盘I/O、和内存方面与B+树索引的成本并不相同。

另外,很多现代数据库为了改善执行计划的成本,可以仅为当前查询动态地生成临时索引。

存取路径

在应用联接运算符(join
operators)之前,你首先需要获得数据。以下就是获得数据的方法。

注:由于所有存取路径的真正问题是磁盘 I/O,我不会过多探讨时间复杂度。

四种类型的Oracle索引扫描介绍

1 全扫描

如果你读过执行计划,一定看到过『全扫描』(或只是『扫描』)一词。简单的说全扫描就是数据库完整的读一个表或索引。就磁盘 I/O 而言,很明显全表扫描的成本比索引全扫描要高昂。

2 范围扫描

其他类型的扫描有索引范围扫描,比如当你使用谓词 ” WHERE AGE > 20 AND AGE < 40 ” 的时候它就会发生。

当然,你需要在 AGE 字段上有索引才能用到索引范围扫描。

在第一部分我们已经知道,范围查询的时间成本大约是
log(N)+M,这里 N 是索引的数据量,M 是范围内估测的行数。多亏有了统计我们才能知道 N 和 M 的值(注: M 是谓词
“ AGE > 20 AND AGE < 40 ” 的选择率)。另外范围扫描时,你不需要读取整个索引,因此在磁盘 I/O 方面没有全扫描那么昂贵。

3 唯一扫描

如果你只需要从索引中取一个值你可以用唯一扫描。

4 根据 ROW ID 存取

多数情况下,如果数据库使用索引,它就必须查找与索引相关的行,这样就会用到根据 ROW ID 存取的方式。

例如,假如你运行:

SELECT LASTNAME, FIRSTNAME
from
PERSON
WHERE
AGE = 28

如果 person 表的 age 列有索引,优化器会使用索引找到所有年龄为 28 的人,然后它会去表中读取相关的行,这是因为索引中只有 age 的信息而你要的是姓和名。

但是,假如你换个做法:

SELECT TYPE_PERSON.CATEGORY from PERSON ,TYPE_PERSON

WHERE PERSON.AGE = TYPE_PERSON.AGE

PERSON 表的索引会用来联接 TYPE_PERSON 表,但是 PERSON 表不会根据行ID 存取,因为你并没有要求这个表内的信息。

虽然这个方法在少量存取时表现很好,这个运算的真正问题其实是磁盘 I/O。假如需要大量的根据行ID存取,数据库也许会选择全扫描。

其它路径

我并没有列举所有的存取路径,如果感兴趣,可以查看各个数据库相关文档。

原文地址:https://www.cnblogs.com/qixinbo/p/9381847.html

时间: 2024-08-29 17:14:15

关系型数据库进阶(二)全局概览的相关文章

关系型数据库进阶(三)连接运算及查询实例

上篇文字,我们知道如何获取数据了,那现在就把它们联接起来! 我要展现的是3个个常用联接运算符:合并联接(Merge join),哈希联接(Hash Join)和嵌套循环联接(Nested Loop Join).但是在此之前,我需要引入新词汇了:内关系和外关系(inner relation and outer relation). 一个关系可以是: 一个表 一个索引 上一个运算的中间结果(比如上一个联接运算的结果). 当你联接两个关系时,联接算法对两个关系的处理是不同的.在本文剩余部分,我将假定:

关系型数据库(二),索引的数据结构

目录 1.二叉查找树 2.B树(B-Tree) 3.B+树(B+-Tree) 4.Hash索引 二.索引的数据结构 1.二叉查找树 2.B树(B-Tree) 二叉搜索树如果数量太大,则进行I/O次数太多,效率也不会太高,所以我们要想办法让树变矮一点,于是就有了B-Tree (1)B-Tree定义 (2)3阶B-Tree 3.B+树(B+-Tree) (1)定义 (2)B+-Tree 4.Hash索引 原文地址:https://www.cnblogs.com/xzmxddx/p/10356008.

关系型数据库之Mysql查询及数据库管理(二)

在关系型数据库之Mysql编译安装及数据库基础(一)我们大致了解的数据库的基本应用了,下面我们来聊聊MySQL的家常吧,在实际生产工作中我需要了解自己再数据库领域应该选择哪条道?这是们走向数据库光明之路的前提,关于数据库发展方向有开发DBA和管理DBA,它们分别需要哪些技能呢: 开发DBA:数据库设计(E-R关系图).SQL开发.内置函数.存储过程(存储过程和存储函数).触发器.事件调查器(even scheduler) 管理DBA:安装.升级.备份.恢复.用户管理.权限管理.监控.分析.基准测

MongoDB学习笔记(二:入门环境配置及与关系型数据库区别总结)

一.下载及安装MongoDB MongoDB下载官网链接:http://www.mongodb.org/downloads 具体安装步骤教程:http://www.shouce.ren/api/view/a/6191(PS:该链接中对MongoDB安装讲解比较详细,通俗易懂.安装教程链接是我从手册网上找到的,下面的总结知识点也是本人从中学习后归纳的一部分,有兴趣的同学不妨收藏一下~) 二.MongoDB可视化工具下载 个人比较习惯在图形化界面进行相关命令操作,所以在安装完MongoDB后,就顺便

Java进阶(二十五)Java连接mysql数据库(底层实现)

Java进阶(二十五)Java连接mysql数据库(底层实现) 前言 很长时间没有系统的使用java做项目了.现在需要使用java完成一个实验,其中涉及到java连接数据库.让自己来写,记忆中已无从搜索.特将之前使用的方法做一简单的总结.也能够在底层理解一下连接数据库的具体步骤. 实现 首先需要导入相关的jar包,我使用的为:mysql-connector-java-5.1.7-bin.jar. 下面来看一下我所使用的数据库连接方法类: MysqlUtil.java package cn.edu

关系型数据库工作原理-事务管理(二)(翻译自Coding-Geek文章)

本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-databases-work/#Buffer-Replacement_strategies 紧接上一篇文章,本文翻译了如下章节: 一. Log manager(日志管理) 通过前面的章节,我们已经知道,为了提升性能,数据库会将数据缓存在内存中.但是,如果在事务提交过程中,数据库服务器崩溃了.缓存在内存的数据就会丢失

ORMBase对象/关系型数据库映射在MVC中的应用(二)

3.DataBase基类,查询方法返回值是List<T>,并且是分页的,ThePart.dll版本2.0中封装了一个PageInfo类,作为分页的类型.这种方法很机械,也很狗血..建议大家不这么用,下面这个例子是项目中同事写的.为了DataBase这个基类的操作数据库返回值各种类型,让人们知道各种情况的数据都可以处理.的这么一个目的,我还是把例子贴出来.我自己都难得去费神看. public static List<SportsBetRecords> SportsGetListPag

二:关系型数据库

一:什么是关系型数据库 关系型数据库是由多张能相互连接的二维行列表格组成的数据库. 二:什么是关系模型 是指二维表格模型,因此一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织 三:当前流行的关系型数据库有哪些 Oracle     DB2     SQL Server   MySql 四:关系型数据库常见操作 数据查询.选择.投影.连接.并 交 差 除数据操作.删除 修改 查询操作 五:SQL(Structured Query Language) 于1974年提出的一种介于关系代数与

【数据库】数据库入门(二): 关系型数据库

定义 属性(Attribute): 表的每一列具有固定的属性类别. 如姓名(Name).年龄(Age).地址(Address).电话号码(PhoneNumber)等. 域(Domain): 每个属性所限定的数据类型及其取值范围. 常用的域有字符串(STRING).日期(DATE).整型数(INT)等. 元组(Tuple): 表的每一行称为一个元组,用来标识一个实体,表中不能出现相同的两个元组. 单个元组的取值必然是所有域的笛卡尔积(Cartesian Product)的一个元素. 如 {'Sun