还记得刚上研究生的时候,导师常挂在嘴边的一句话,“科研的基础不过就是数据而已。”如今看来,无论是人文社科,还是自然科学,或许都可在一定程度上看作是数据的科学。
倘若剥开研究领域的外衣,将人的操作抽象出来,那么科研的过程大概就是根据数据流动探索其中的未知信息吧。当然科学研究的范畴涵盖甚广,也不是一两句话能够拎得清的。不过从这个角度上的阐述,也只是为了引出数据的重要性。
在当今社会,充斥着大量的数据。从众多APP上的账户资料到银行信用体系等个人档案,都离不开对大量数据的组织、存储和管理。而这,便是数据库存在的目的和价值。
目前数据库的类型主要分为两种,一种是关系型数据库,另一种是非关系型数据库(NoSQL)。而我们今天的主角MySQL就是关系型数据库中的一种。
1 关系型数据库与NoSQL
关系型数据库,顾名思义,是指存储的数据之间具有关系。这种所谓的关系通常用二维表格中的行列来表示,即一个二维表的逻辑结构能够反映表中数据的存储关系。概念总是拗口难懂的。那么简单来说,关系型数据库的存储就是按照表格进行的。数据的存储实际上就是对一个或者多个表格的存储。通过对这些表格进行分类、合并、连接或者选取等运算来实现对数据库的管理。常见的关系型数据库有MySQL、Oracle、DB2和SqlServer等。非关系型数据库(NoSQL)是相对于关系型数据库的一种泛指,它的特点是去掉了关系型数据库中的关系特性,从而可获得更好的扩展性。NoSQL并没有严格的存储方式,但采用不同的存储结构都是为了获得更高的性能和更高的并发。NoSQL根据存储方式可分为四大类,键值存储数据库、列存储数据库、文档型数据库和图形数据库。这四种数据的存储原理不尽相同,因而在应用场景上也有些许的差异。一般常用的有作为数据缓存的redis和分布式系统的HBase。目前常见的数据库排名可见网站:
https://db-engines.com/en/ranking
关系型数据库与非关系型数据库本质上的区别就在于存储的数据是否具有一定的逻辑关系,由此产生的两类数据库看的性能和优劣势上也有一定的区别。二者对比可见下图。
2 MySQL简介
介绍
在关系型数据库中,MySQL可以说是其中的王者。它是目前最流行的数据库之一,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL数据库具有以下几个方面的优势:
- 体积小、速度快;
- 代码开源,采用了 GPL 协议,可以修改源码来开发自己的 MySQL 系统;
- 支持大型的数据库,可以处理拥有上千万条记录的大型数据库;
- 使用标准的 SQL 数据语言形式,并采用优化的 SQL 查询算法,有效地提高查询速度;
- 使用 C 和 C++ 编写,并使用多种编译器进行测试,保证源代码的可移植性;
- 可运行在多个系统上,并且支持多种语言;
- 核心程序采用完全的多线程编程,可以灵活地为用户提供服务,充分利用CPU资源。
逻辑架构
MySQL的逻辑架构可分为四层,包括连接层、服务层、引擎层和存储层,各层的接口交互及作用如下图所示。需要注意的是,由于本文将主要讲解事务的实现原理,因此下文针对的都是InnoDB引擎下的情况。
连接层: 负责处理客户端的连接以及权限的认证。 服务层: 定义有许多不同的模块,包括权限判断,SQL接口,SQL解析,SQL分析优化, 缓存查询的处理以及部分内置函数执行等。MySQL的查询语句在服务层内进行解析、优化、缓存以及内置函数的实现和存储。 引擎层: 负责MySQL中数据的存储和提取。MySQL中的服务器层不管理事务,事务是由存储引擎实现的。其中使用最为广泛的存储引擎为InnoDB,其它的引擎都不支持事务。 存储层: 负责将数据存储与设备的文件系统中。
3 MySQL事务
事务是MySQL区别于NoSQL的重要特征,是保证关系型数据库数据一致性的关键技术。事务可看作是对数据库操作的基本执行单元,可能包含一个或者多个SQL语句。这些语句在执行时,要么都执行,要么都不执行。事务的执行主要包括两个操作,提交和回滚。
提交:commit,将事务执行结果写入数据库。 回滚:rollback,回滚所有已经执行的语句,返回修改之前的数据。
MySQL事务包含四个特性,号称ACID四大天王。
原子性(Atomicity) :语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log日志实现的。 持久性(Durability :保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log日志。 隔离性(Isolation) :保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制、数据的隐藏列、undo log和类next-key lock机制。 一致性(Consistency) :事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障。
原子性
事务的原子性就如原子操作一般,表示事务不可再分,其中的操作要么都做,要么都不做;如果事务中一个SQL语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。只有0和1,没有其它值。事务的原子性表明事务就是一个整体,当事务无法成功执行的时候,需要将事务中已经执行过的语句全部回滚,使得数据库回归到最初未开始事务的状态。事务的原子性就是通过undo log日志进行实现的。当事务需要进行回滚时,InnoDB引擎就会调用undo log日志进行SQL语句的撤销,实现数据的回滚。
持久性
事务的持久性是指当事务提交之后,数据库的改变就应该是永久性的,而不是暂时的。这也就是说,当事务提交之后,任何其它操作甚至是系统的宕机故障都不会对原来事务的执行结果产生影响。事务的持久性是通过InnoDB存储引擎中的redo log日志来实现的,具体实现思路见下文。
隔离性
原子性和持久性是单个事务本身层面的性质,而隔离性是指事务之间应该保持的关系。隔离性要求不同事务之间的影响是互不干扰的,一个事务的操作与其它事务是相互隔离的。由于事务可能并不只包含一条SQL语句,所以在事务的执行期间很有可能会有其它事务开始执行。因此多事务的并发性就要求事务之间的操作是相互隔离的。这一点跟多线程之间数据同步的概念有些类似。锁机制事务之间的隔离,是通过锁机制实现的。当一个事务需要对数据库中的某行数据进行修改时,需要先给数据加锁;加了锁的数据,其它事务是不运行操作的,只能等待当前事务提交或回滚将锁释放。锁机制并不是一个陌生的概念,在许多场景中都会利用到不同实现的锁对数据进行保护和同步。而在MySQL中,根据不同的划分标准,还可将锁分为不同的种类。
按照粒度划分:行锁、表锁、页锁 按照使用方式划分:共享锁、排它锁 按照思想划分:悲观锁、乐观锁
锁机制的知识点很多,由于篇幅不好全部展开讲。这里对按照粒度划分的锁进行简单介绍。
粒度:指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。
MySQL按照锁的粒度划分可以分为行锁、表锁和页锁。
行锁:粒度最小的锁,表示只针对当前操作的行进行加锁; 表锁:粒度最大的锁,表示当前的操作对整张表加锁; 页锁:粒度介于行级锁和表级锁中间的一种锁,表示对页进行加锁。
这三种锁是在不同层次上对数据进行锁定,由于粒度的不同,其带来的好处和劣势也不一而同。
表锁在操作数据时会锁定整张表,因而并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。
MySQL中不同的存储引擎能够支持的锁也是不一样的。MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。并发读写问题在并发情况下,MySQL的同时读写可能会导致三类问题,脏读、不可重复度和幻读。
(1)脏读:当前事务中读到其他事务未提交的数据,也就是脏数据。
以上图为例,事务A在读取文章的阅读量时,读取到了事务B为提交的数据。如果事务B最后没有顺利提交,导致事务回滚,那么实际上阅读量并没有修改成功,而事务A却是读到的修改后的值,显然不合情理。
(2)不可重复读:在事务A中先后两次读取同一个数据,但是两次读取的结果不一样。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
以上图为例,事务A在先后读取文章阅读量的数据时,结果却不一样。说明事务A在执行的过程中,阅读量的值被其它事务给修改了。这样使得数据的查询结果不再可靠,同样也不合实际。
(3)幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的行数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
以上图为例,当对0<阅读量<100的文章进行查询时,先查到了一个结果,后来查询到了两个结果。这表明同一个事务的查询结果数不一,行数不一致。这样的问题使得在根据某些条件对数据筛选的时候,前后筛选结果不具有可靠性。
隔离级别
根据上面这三种问题,产生了四种隔离级别,表明数据库不同程度的隔离性质。
在实际的数据库设计中,隔离级别越高,导致数据库的并发效率会越低;而隔离级别太低,又会导致数据库在读写过程中会遇到各种乱七八糟的问题。
因此在大多数数据库系统中,默认的隔离级别时读已提交(如Oracle)或者可重复读RR(MySQL的InnoDB引擎)。