关系数据库RDBMS关键技术
RDBMS(关系型数据库)是目前使用最为广泛的数据库之一,同时也是整个信息化时代的基石。本文章通过生活中常见的场景向大家介绍RDBMS的作用、发展历程等。
RDBMS 事务 ACID
RDBMS
事务(Transaction): 是由一组SQL语句组成的一个程序执行单元(Unit),它需要满足ACID特性。
sql
BEGIN;
UPDATE account table SET balance = balance - '小目标' WHERE name = '抖音';
UPDATE account table SET balance = balance + '小目标' WHERE name = '小明';
COMMIT;
ACID
原子性(Atomicity): 事务是一个不可再分割的工作单元,事务中的操作要么都发生,要么都不发生。
一致性(Consistency): 数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
隔离性(solation): 多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
持久性(Durability): 在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
发展历史
传统的文件系统难以应对数据增长的挑战,也无法满足多用户共享数据和快速检索数据的需求。
层次型、网状型和关系型数据库划分的原则是数据之间的联系方式。层次数据库是按记录来存取数据的,网状数据库是采用网状原理和方法来存储数据;关系型数据库是以行和列的形式存储数据。
网状数据模型是以记录类型为结点的网络结构,即一个结点可以有一个或多个下级结点,也可以有一个或多个上级结点,两个结点之间甚至可以有多种联系,例如"教师"与"课程"两个记录类型,可以有"任课"和"辅导"两种联系,称之为复合链。
两个记录类型之间的值可以是多对多的联系,例如一门课程被多个学生修读,一个学生选修多门课程。
层次数据库就是树结构。每棵树都有且仅有一个根节点,其余的节点都是非根节点。每个节点表示一个记录类型对应与实体的概念,记录类型的各个字段对应实体的各个属性各个记录类型及其字段都必须记录。
使用表格表示实体和实体之间关系的数据模型称之为关系数据模型。
关系数据模型中,无论是是实体、还是实体之间的联系都是被映射成统---的关系---一张二维表,在关系模型中,操作的对象和结果都是一张二维表,它由行和列组成;关系型数据库可用于表示实体之间的多对多的关系,只是此时要借助第三个关系---表,来实现多对多的关系;
1974年ACM牵头组织了一次研讨会,会上开展了一场分别以Codd和Bachman为首的支持和反对关系数据库两派之间的辩论。这次著名的辩论推动了关系数据库的发展,使其最终成为现代数据库产品的主流。
高度非过程化****
非关系数据模型的数据操纵语言是面向过程的语言,用其完成用户请求时,必须指定存取路径。而用SQL进行数据操作,用户只需提出"做什么",而不必指明"怎么做",因此用户无须了解存取路径,存取路径的选择以及SQL语句的操作过程由系统自动完成。这不但大大减轻了用户负担,而且有利于提高数据独立性。
面向集合的操作方式****
SQL采用集合操作方式,不仅查找结果可以是元组的集合,而且一次插入、删除、更新操作的对象也可以是元组的集合。
语言简洁,易学易用****
SQL功能极强,但由于设计巧妙,语言十分简洁,完成数据定义、数据操纵、数据控制的核心功能只用了9个动词:CREATE、ALITER、DROP、SEIECT、INSERT、UPDATE.DELETE、GRANT、REVOKE。且SQL语言语法简单,接近英语口语,因此容易学习,也容易使用。
关键技术
数据库发展的核心:
- ①SQL引擎:解决SQL的问题
- ②存储引擎:解决数据存储的问题
- ③事务引擎:解决事务ACID的问题
SQL引擎
查询解析:SQL语言接近自然语言,入门容易。但是各种关键字、操作符组合起来,可以表达丰富的语意。因此想要处理SQL命令,首先将文本解析成结构化数据,也就是抽象语法树(AST)。
查询优化:SQL是一门表意的语言,只是说『要做什么』,而不说『怎么做』。所以需要一些复杂的逻辑选择『如何拿数据』,也就是选择一个好的查询计划。优化器的作用根据AST优化产生最优执行计划(Plan Tree) 。
查询执行:根据查询计划,完成数据读取、处理、写入等操作。
事务引擎:处理事务一致性、并发、读写隔离等
存储引擎:内存中的数据缓存区、数据文件、日志文件
SQL完整执行流程
举例:抖音发放一个亿的小目标:手机发起抢红包的操作请求,后台路由器收到请求,发送一条SQL给数据库,数据库接收到这条SQL之后,首先要解析SQL(SQL语言对人友好但对机器并不友好),Parser是语法解析器,把SQL的语句生成一个AST语法树。语法树给到优化器Optimizer,优化器根据语法树生成SQL到底怎么执行,它的产物是Plan(是一个树状结构的Plan tree)。接下来是执行器Executor,它拿着Plan tree去做真正的执行,执行时会从文件中读取数据,然后返回数据,也会写入数据同时写入日志,然后将所有数据一步步返回给用户
其中:
- Parser、Optimizer、Executor都是SQL引擎
- DataFile、Log File都是存储引擎
- 写日志本身是为事务所做的准备
SQL引擎-Parser
所有的代码在执行之前,都存在一个解析编译的过程,差异点无非在于是静态解析编译还是动态的。SQL语言也类似,在SQL查询执行前的第一步就是查询解析。
词法分析:将一条SQL语句对应的字符串分割为一个个token,这些token可以简单分类。
语法分析:把词法分析的结果转为语法树。根据token序列匹配不同的语法规则,比如这里匹配的是update语法规则,类似的还有insert、delete、 select、create、drop等等语法规则。根据语法规则匹配SQL语句中的关键字,最终输出一个结构化的数据结构。
语义分析:对语法树中的信息进行合法性校验。
SQL引擎-Optimizer
比如红绿灯最少,或不拥堵,都是一个规则
到达一个目的地,有不同的路线,选择不同的路线有不同的代价。这里的代价可能是时间,也可能是路程。比如我们赶时间的时候,就会选择时间最短的。如果时间没那么赶,那么我们可能选择路程最短的。因为这样省油啊,毕竟现在油价这么高。
对于数据库也是这样,一个查询有不同的执行方案。
那对于数据库而言,什么是一条SQL执行的代价呢?
其实,对于用户只能感知到查询时间这个代价,底层用了多少资源他是不在乎的。但是在并发的情况下,就得考虑资源消耗了,这个用户的查询占用的资源多了,其他用户的资源就少了。所以资源也是必须考虑的一点。
对于InnoDB存储引擎来说,全表扫描的意思就是把聚簇索引中的记录都依次和给定的搜索条件做一下比较,把符合搜索条件的记录加入到结果集,所以需要将聚簇孝引对应的页面加载到内存中,然后再检测记录是否符合搜索条件。
对于使用二级索引+回表方式的查询,设计MySQL的大叔计算这种查询的成本依赖两个方面的数据:范围区间数量,需要回表数据量
SQL引擎-Executer
Plan Tree为基础
调用关系是由根到叶
数据流是从叶到根
火山模型:逐层向下调用,数据逐层向上返回。
请求是一步步从根结点到叶结点,函数栈是一层层往下调用的:首先用户查询一个请求(next请求),查询树的最上层需要做投影然后过滤,底层去访问数据Engine。
数据返回是一层层往上返回。
由于火山模型的缺点,因此接下来有了 向量化、编译执行 的模型。
向量化执行更适合于大批量数据处理,对于很多单行数据处理并没有优势。而且往往搭配列式存储使用。
返回的是一批row,即batch
代码生成之后数据库运行时仍然是一个for循环,只不过这个循环内部的代码从简单的一个虚函数调用plan.next)展开成了一系列具体的运算逻辑,这样数据就不用再各个operator之间进行传递,而且有些数据还可以直接被存放在寄存器中,进一步提升系统性能。整个操作有点像imnline函数,把所有的操作inline到一个函数中去。
LLVM动态编译执行技术,根据优化器产生的计划,动态的生成执行代码。
所有逻辑代码写到一个函数里面,但要用到动态编译执行技术。
存储引擎-InnoDB
- System Tablespace 存储元信息
- General Tablespace 存储用户真实数据
- Undo Tablespace和Redo Log 存储事物所用到的日志
存储引擎-Buffer Pool
MySQL中每个chunk的大小一般为128M,每个block对应一个page,一个chunk下面有8192个block。这样可以避免内存碎片化分成多个instance,可以有效避免并发冲突。
Paae id % instance num得到它属干哪个instance
当buffer pool里的页面都被使用之后,再需要换存其他页面怎么办?淘汰已有的页面
基于什么规则淘汰:淘汰那个最近一段时间最少被访问过的缓存页了,这种思想就是典型的LRU算法了。
普通的LRU算法存在缺陷,考虑我们需要扫描10GB的表,而我们的buffer pool只有1GB,这样就会因为全表扫描的数据量大,需要淘汰的缓存页多,导致在淘汰的过程中,极有可能将需要频繁使用到的缓存页给淘汰了,而放进来的新数据却是使用频率很低的数据。
MySQL确实没有直接使用LRU算法,而是在LRU 算法上进行了优化。
MySQL的优化思路就是:对数据进行冷热分离,将LRU链表分成两部分,一部分用来存放冷数据,也就是刚从磁盘读进来的数据,另一部分用来存放热点数据,也就是经常被访问到数据。
当从磁盘读取数据页后,会先将数据页存放到LRU链表冷数据区的头部,如果这些缓存页在1秒之后被访问,那么就将缓存页移动到热数据区的头部;如果是1秒之内被访问则不会移动,缓存页仍然处于冷数据区中。
淘汰时,首先淘汰冷数据区。
存储引擎-Page
- User Records在页面上实际是无序的,通过一个单向链表连接
- 一条数据在页面上怎么存储:
- 变长字段列表:需要知道哪些字段是变长的,具体是多长
- NULL值标志位:标志哪些列数据是空的,哪些是有数据的
- row_id:这行数据的id信息,是持续递增的数据
- trx_id:事务id信息,也是递增的数据,每行数据都有对应的自己是属于哪个事务id的
- roll_ptr:指向undo的数据
- Col:最右面是一列一列的数据
- Header:图上有
- Directory:二分查找
存储引擎-B+Tree
B+Tree:二分查找的扩展
事务引擎
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollack)到事务开
始前的状态,就像这个事务从来没有执行过一样。
需要记录数据修改前的状态,一边在事务失败时进行回滚。
undo log是逻辑日志,记录的是数据的增量变化,它的作用是保证事务的原子性和事务并发控制。可以用于事务回滚,以及提供多版本机制(MCC),解决读写冲突和一致性读的问题。
lsolation(隔离性)︰数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。如果多个并发事务访问同一行记录,就需要锁机制来保证了。
读写是否冲突?读写互不阻塞,MVCC机制。
脏读:事务还没提交之前,它对数据做的修改,不应该被其他人看到。
万一抖音给我的账户转账的事务还没完成,羊老师就查到了账户上有一个亿,后来抖音发现不对,把这个事务回滚掉了。过一会羊老师发现自己账户的一个亿又没了,去找
银行要个说法,结果被保安赶了出来。
持久化:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
WAL:修改并不直接写入到数据库文件中,而是写入到另外一个称为WAL的文件中;如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。
优点:
只记录增量变化,没有写放大
Append only,没有随机IO