大家好,我是阿秀。
我来更新了,本期是 MySQL 第二期,至此 MySQL 部分就全部更新完毕了,下一弹就是 Redis 篇了。
上一篇文章中,小伙伴建议将资料按照更细粒度去整理一番,我觉得是非常不错的建议。目前正在整理中,等全部整理完毕就会更新第四版的 PDF 版本了,第三版的 PDF 直接回复关键字 「PDF」 就可以下载了。
话不多说,那我们就开始本期内容吧。
26、数据库三大范式精讲
第一范式
在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。
如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。
简而言之,第一范式就是无重复的列。
第二范式
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。
为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。第二范式(2NF)要求实体的属性完全依赖于主关键字。
所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。
简而言之,第二范式就是非主属性非部分依赖于主关键字。
第三范式
满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。
例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。
简而言之,第三范式就是属性不依赖于其它非主属性。
27、数据库三大范式精要总结
(1)简单归纳:
第一范式(1NF):字段不可分;
第二范式(2NF):有主键,非主键字段依赖主键;
第三范式(3NF):非主键字段不能相互依赖。
(2)解释:
1NF:原子性。字段不可再分,否则就不是关系数据库;;
2NF:唯一性 。一个表只说明一个事物;
3NF:每列都与主键有直接关系,不存在传递依赖。
28、MySQL常见的存储引擎InnoDB、MyISAM的区别?适用场景分别是?
1)事务:MyISAM不支持,InnoDB支持
2)锁级别:MyISAM 表级锁,InnoDB 行级锁及外键约束
3)MyISAM存储表的总行数;InnoDB不存储总行数;
4)MyISAM采用非聚集索引,B+树叶子存储指向数据文件的指针。InnoDB主键索引采用聚集索引,B+树叶子存储数据
适用场景:
MyISAM适合:插入不频繁,查询非常频繁,如果执行大量的SELECT,MyISAM是更好的选择, 没有事务。
InnoDB适合:可靠性要求比较高,或者要求事务;表更新和查询都相当的频繁, 大量的INSERT或UPDATE
29、事务四大特性(ACID)原子性、一致性、隔离性、持久性?
第一种回答
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
第二种回答
原子性(Atomicity)
- 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistency)
- 事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不可能A扣了钱,B却没收到。
隔离性(Isolation)
- 隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性(Durability)
- 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
30、SQL中的NOW()和CURRENT_DATE()两个函数有什么区别?
NOW()命令用于显示当前年份,月份,日期,小时,分钟和秒。CURRENT_DATE()仅显示当前年份,月份和日期。
31、什么是聚合索引 ?
聚簇索引就是按照拼音查询,非聚簇索引就是按照偏旁等来进行查询。
其实,我们的汉语字典的正文本身就是一个聚集索引。比如,我们要查"安"字,就会很自然地翻开字典的前几页,因为"安"的拼音是"an",而按照拼音排序 汉字的字典是以英文字母"a"开头并以"z"结尾的,那么"安"字就自然地排在字典的前部。如果您翻完了所有以"a"开头的部分仍然找不到这个字,那么就 说明您的字典中没有这个字;同样的,如果查"张"字,那您也会将您的字典翻到最后部分,因为"张"的拼音是"zhang"。也就是说,字典的正文部分本身 就是一个目录,您不需要再去查其他目录来找到您需要找的内容。
我们把这种正文内容本身就是一种按照一定规则排列的目录称为"聚集索引"
32、什么是非聚合索引?
如果您认识某个字,您可以快速地从自动中查到这个字。但您也可能会遇到您不认识的字,不知道它的发音,这时候,您就不能按照刚才的方法找到您要查的字,而 需要去根据"偏旁部首"查到您要找的字,然后根据这个字后的页码直接翻到某页来找到您要找的字。
但您结合"部首目录"和"检字表"而查到的字的排序并不是 真正的正文的排序方法,比如您查"张"字,我们可以看到在查部首之后的检字表中"张"的页码是672页,检字表中"张"的上面是"驰"字,但页码却是63 页,"张"的下面是"弩"字,页面是390页。
很显然,这些字并不是真正的分别位于"张"字的上下方,现在您看到的连续的"驰、张、弩"三字实际上就是他 们在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。
我们可以通过这种方式来找到您所需要的字,但它需要两个过程,先找到目录中的结果,然后 再翻到您所需要的页码。
我们把这种目录纯粹是目录,正文纯粹是正文的排序方式称为"非聚集索引"。
33、聚集索引与非聚集索引的区别是什么?
非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据, 而通过非聚集索引可以查到记录对应的主键值 , 再使用主键的值通过聚集索引查找到需要的数据。聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致。
聚集索引(Innodb)的叶节点就是数据节点,而非聚集索引(MyisAM)的叶节点仍然是索引节点,只不过其包含一个指向对应数据块的指针。
34、创建索引时需要注意什么?
非空字段:应该指定列为NOT NULL,除非你想存储NULL。在 MySQL 中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;
取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。唯一、不为空、经常被查询的字段 的字段适合建索引
35、MySQL中CHAR和VARCHAR的区别有哪些?
- char的长度是不可变的,用空格填充到指定长度大小,而varchar的长度是可变的。
- char的存取数度还是要比varchar要快得多
- char的存储方式是:对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节。varchar的存储方式是:对每个英文字符占用2个字节,汉字也占用2个字节。
36、MySQL 索引使用的注意事项
MySQL 索引通常是被用于提高 WHERE 条件的数据行匹配时的搜索速度,在索引的使用过程中,存在一些使用细节和注意事项。
函数,运算,否定操作符,连接条件,多个单列索引,最左前缀原则,范围查询,不会包含有NULL值的列,like 语句不要在列上使用函数和进行运算
1)不要在列上使用函数,这将导致索引失效而进行全表扫描。
select * from news where year(publish_time) < 2017
为了使用索引,防止执行全表扫描,可以进行改造。
select * from news where publish_time < '2017-01-01'
还有一个建议,不要在列上进行运算,这也将导致索引失效而进行全表扫描。
select * from news where id / 100 = 1
为了使用索引,防止执行全表扫描,可以进行改造。
select * from news where id = 1 * 100
2)尽量避免使用 != 或 not in或 <> 等否定操作符
应该尽量避免在 where 子句中使用 != 或 not in 或 <> 操作符,因为这几个操作符都会导致索引失效而进行全表扫描。尽量避免使用 or 来连接条件 应该尽量避免在 where 子句中使用 or 来连接条件,因为这会导致索引失效而进行全表扫描。
select * from news where id = 1 or id = 2
3)多个单列索引并不是最佳选择
MySQL 只能使用一个索引,会从多个索引中选择一个限制最为严格的索引,因此,为多个列创建单列索引,并不能提高 MySQL 的查询性能。假设,有两个单列索引,分别为 news_year_idx(news_year) 和 news_month_idx(news_month)。现在,有一个场景需要针对资讯的年份和月份进行查询,那么,SQL 语句可以写成:
select * from news where news_year = 2017 and news_month = 1
事实上,MySQL 只能使用一个单列索引。为了提高性能,可以使用复合索引 news_year_month_idx(news_year, news_month) 保证 news_year 和 news_month 两个列都被索引覆盖。
4)复合索引的最左前缀原则
复合索引遵守“最左前缀”原则,即在查询条件中使用了复合索引的第一个字段,索引才会被使用。因此,在复合索引中索引列的顺序至关重要。如果不是按照索引的最左列开始查找,则无法使用索引。假设,有一个场景只需要针对资讯的月份进行查询,那么,SQL 语句可以写成:
select * from news where news_month = 1
此时,无法使用 news_year_month_idx(news_year, news_month) 索引,因为遵守“最左前缀”原则,在查询条件中没有使用复合索引的第一个字段,索引是不会被使用的。
5)覆盖索引的好处
如果一个索引包含所有需要的查询的字段的值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能。因此,可以定义一个让索引包含的额外的列,即使这个列对于索引而言是无用的。
6)范围查询对多列查询的影响
查询中的某个列有范围查询,则其右边所有列都无法使用索引优化查找。举个例子,假设有一个场景需要查询本周发布的资讯文章,其中的条件是必须是启用状态,且发布时间在这周内。那么,SQL 语句可以写成:
select * from news where publish_time >= '2017-01-02' and publish_time <= '2017-01-08' and enable = 1
这种情况下,因为范围查询对多列查询的影响,将导致 news_publish_idx(publish_time, enable) 索引中 publish_time 右边所有列都无法使用索引优化查找。换句话说,news_publish_idx(publish_time, enable) 索引等价于 news_publish_idx(publish_time) 。对于这种情况,我的建议:对于范围查询,务必要注意它带来的副作用,并且尽量少用范围查询,可以通过曲线救国的方式满足业务场景。例如,上面案例的需求是查询本周发布的资讯文章,因此可以创建一个news_weekth 字段用来存储资讯文章的周信息,使得范围查询变成普通的查询,SQL 可以改写成:
select * from news where news_weekth = 1 and enable = 1
然而,并不是所有的范围查询都可以进行改造,对于必须使用范围查询但无法改造的情况,我的建议:不必试图用 SQL 来解决所有问题,可以使用其他数据存储技术控制时间轴,例如 Redis 的 SortedSet 有序集合保存时间,或者通过缓存方式缓存查询结果从而提高性能。
7)索引不会包含有NULL值的列
只要列中包含有 NULL 值都将不会被包含在索引中,复合索引中只要有一列含有 NULL值,那么这一列对于此复合索引就是无效的。因此,在数据库设计时,除非有一个很特别的原因使用 NULL 值,不然尽量不要让字段的默认值为 NULL。
8)隐式转换的影响
当查询条件左右两侧类型不匹配的时候会发生隐式转换,隐式转换带来的影响就是可能导致索引失效而进行全表扫描。下面的案例中,date_str 是字符串,然而匹配的是整数类型,从而发生隐式转换。
select * from news where date_str = 201701
因此,要谨记隐式转换的危害,时刻注意通过同类型进行比较。
9)like 语句的索引失效问题
like 的方式进行查询,在 like “value%” 可以使用索引,但是对于 like “%value%” 这样的方式,执行全表查询,这在数据量小的表,不存在性能问题,但是对于海量数据,全表扫描是非常可怕的事情。所以,根据业务需求,考虑使用 ElasticSearch 或 Solr 是个不错的方案。
37、MySQL中有哪些索引?有什么特点?
- 普通索引:仅加速查询
- 唯一索引:加速查询 + 列值唯一(可以有null)
- 主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
- 组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
- 全文索引:对文本的内容进行分词,进行搜索
- 索引合并:使用多个单列索引组合搜索
- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖
- 聚簇索引:表数据是和主键一起存储的,主键索引的叶结点存储行数据(包含了主键值),二级索引的叶结点存储行的主键值。使用的是B+树作为索引的存储结构,非叶子节点都是索引关键字,但非叶子节点中的关键字中不存储对应记录的具体内容或内容地址。叶子节点上的数据是主键与具体记录(数据内容)
38、既然索引有那么多优点,为什么不对表总的每一列创建一个索引呢?
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
39、索引如何提高查询速度的
将无序的数据变成相对有序的数据(就像查有目的一样)
40、使用索引的注意事项
-
在经常需要搜索的列上,可以加快搜索的速度;
-
在经常使用在where子句中的列上面创建索引,加快条件的判断速度。
-
将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描
-
在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间
-
避免where子句中对字段施加函数,这会造成无法命中索引
-
在中到大型表索引都是非常有效的,但是特大型表的维护开销会很大,不适合建索引,建立用逻辑索引
-
在经常用到连续的列上,这些列主要是由一些外键,可以加快连接的速度
-
与业务无关时多使用逻辑主键,也就是自增主键在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
-
删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗
-
在使用limit offset查询缓存时,可以借助索引来提高性能。
41、增加B+树的路数可以降低树的高度,那么无限增加树的路数是不是可以有最优的查找效率?
不可以。因为这样会形成一个有序数组,文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中。有序数组没法一次性加载进内存,这时候B+树的多路存储威力就出来了,可以每次加载B+树的一个结点,然后一步步往下找,
42、说一下数据库表锁和行锁吧
表锁
不会出现死锁,发生锁冲突几率高,并发低。
MyISAM在执行查询语句(select)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL的表级锁有两种模式:表共享读锁和表独占写锁。
读锁会阻塞写,写锁会阻塞读和写
- 对MyISAM表的读操作,不会阻塞其它进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
- 对MyISAM表的写操作,会阻塞其它进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
MyISAM不适合做写为主表的引擎,因为写锁后,其它线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
行锁
会出现死锁,发生锁冲突几率低,并发高。
在MySQL的InnoDB引擎支持行锁,与Oracle不同,MySQL的行锁是通过索引加载的,也就是说,行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁,此时其它事务无法对当前表进行更新或插入操作。
行锁的实现需要注意:
- 行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
- 两个事务不能锁同一个索引。
- insert,delete,update在事务中都会自动默认加上排它锁。
行锁的适用场景:
A用户消费,service层先查询该用户的账户余额,若余额足够,则进行后续的扣款操作;这种情况查询的时候应该对该记录进行加锁。
否则,B用户在A用户查询后消费前先一步将A用户账号上的钱转走,而此时A用户已经进行了用户余额是否足够的判断,则可能会出现余额已经不足但却扣款成功的情况。
为了避免此情况,需要在A用户操作该记录的时候进行for update加锁
43、SQL语法中内连接、自连接、外连接(左、右、全)、交叉连接的区别分别是什么?
内连接:只有两个元素表相匹配的才能在结果集中显示。
外连接:左外连接: 左边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
右外连接:右边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。全外连接:连接的表中不匹配的数据全部会显示出来。
交叉连接:笛卡尔效应,显示的结果是链接表数的乘积。
44、你知道哪些数据库结构优化的手段?
- 范式优化:比如消除冗余(节省空间。。)
- 反范式优化:比如适当加冗余等(减少join)
- 限定数据的范围:务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。
- 读/写分离:经典的数据库拆分方案,主库负责写,从库负责读;
- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。
45、数据库优化中有一个比较常用的手段就是把数据表进行拆分,关于拆分数据表你了解哪些?
拆分其实又分垂直拆分和水平拆分
案例:简单购物系统暂设涉及如下表:
1.产品表(数据量10w,稳定)
2.订单表(数据量200w,且有增长趋势)
3.用户表 (数据量100w,且有增长趋势)
以 MySQL 为例讲述下水平拆分和垂直拆分,MySQL能容忍的数量级在百万静态数据可以到千万
垂直拆分
解决问题:表与表之间的io竞争
不解决问题:单表中数据量增长出现的压力
方案:把产品表和用户表放到一个server上 订单表单独放到一个server上
水平拆分
解决问题:单表中数据量增长出现的压力
不解决问题:表与表之间的io争夺
方案:用户表 通过性别拆分为男用户表和女用户表,订单表 通过已完成和完成中拆分为已完成订单和未完成订单,产品表 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。
46、为什么MySQL索引要使用B+树,而不是B树或者红黑树?
我们在MySQL中的数据一般是放在磁盘中的,读取数据的时候肯定会有访问磁盘的操作,磁盘中有两个机械运动的部分,分别是盘片旋转和磁臂移动。盘片旋转就是我们市面上所提到的多少转每分钟,而磁盘移动则是在盘片旋转到指定位置以后,移动磁臂后开始进行数据的读写。那么这就存在一个定位到磁盘中的块的过程,而定位是磁盘的存取中花费时间比较大的一块,毕竟机械运动花费的时候要远远大于电子运动的时间。当大规模数据存储到磁盘中的时候,显然定位是一个非常花费时间的过程,但是我们可以通过B树进行优化,提高磁盘读取时定位的效率。
为什么B类树可以进行优化呢?我们可以根据B类树的特点,构造一个多阶的B类树,然后在尽量多的在结点上存储相关的信息,保证层数(树的高度)尽量的少,以便后面我们可以更快的找到信息,磁盘的I/O操作也少一些,而且B类树是平衡树,每个结点到叶子结点的高度都是相同,这也保证了每个查询是稳定的。
特别地:只有B-树和B+树,这里的B-树是叫B树,不是B减树,没有B减树的说法。
47、为什么MySQL索引采用B+树而不用hash表和B树?
- 利用Hash需要把数据全部加载到内存中,如果数据量大,是一件很消耗内存的事,而采用B+树,是基于按照节点分段加载,由此减少内存消耗。
- 和业务场景有段,对于唯一查找(查找一个值),Hash确实更快,但数据库中经常查询多条数据,这时候由于B+数据的有序性,与叶子节点又有链表相连,他的查询效率会比Hash快的多。
- b+树的非叶子节点不保存数据,只保存子树的临界值(最大或者最小),所以同样大小的节点,b+树相对于b树能够有更多的分支,使得这棵树更加矮胖,查询时做的IO操作次数也更少。
48、MySQL中存储索引用到的数据结构是B+树,B+树的查询时间跟树的高度有关,是log(n),如果用hash存储,那么查询时间是O(1)。既然hash比B+树更快,为什么MySQL用B+树来存储索引呢?
一、从内存角度上说,数据库中的索引一般是在磁盘上,数据量大的情况可能无法一次性装入内存,B+树的设计可以允许数据分批加载。
二、从业务场景上说,如果只选择一个数据那确实是hash更快,但是数据库中经常会选中多条,这时候由于B+树索引有序,并且又有链表相连,它的查询效率比hash就快很多了。
49、关系型数据库的四大特性在得不到保障的情况下会怎样?
ACID,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
我们以从A账户转账50元到B账户为例进行说明一下ACID这四大特性。
原子性
原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。即要么转账成功,要么转账失败,是不存在中间的状态!
如果无法保证原子性会怎么样?
OK,就会出现数据不一致的情形,A账户减去50元,而B账户增加50元操作失败。系统将无故丢失50元~
一致性
一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。那什么是合法的数据状态呢?这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!
如果无法保证一致性会怎么样?
- 例一:A账户有200元,转账300元出去,此时A账户余额为-100元。你自然就发现了此时数据是不一致的,为什么呢?因为你定义了一个状态,余额这列必须大于0。
- 例二:A账户200元,转账50元给B账户,A账户的钱扣了,但是B账户因为各种意外,余额并没有增加。你也知道此时数据是不一致的,为什么呢?因为你定义了一个状态,要求A+B的余额必须不变。
隔离性
隔离性是指多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
如果无法保证隔离性会怎么样?
假设A账户有200元,B账户0元。A账户往B账户转账两次,金额为50元,分别在两个事务中执行。如果无法保证隔离性,A可能就会出现扣款两次的情形,而B只加款一次,凭空消失了50元,依然出现了数据不一致的情形!
持久性
根据定义,持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
如果无法保证持久性会怎么样?
在MySQL中,为了解决CPU和磁盘速度不一致问题,MySQL是将磁盘上的数据加载到内存,对内存进行操作,然后再回写磁盘。好,假设此时宕机了,在内存中修改的数据全部丢失了,持久性就无法保证。
设想一下,系统提示你转账成功。但是你发现金额没有发生任何改变,此时数据出现了不合法的数据状态,我们将这种状态认为是数据不一致的情形。
50、数据库如何保证一致性?
分为两个层面来说。
- 从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。
- 从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据!
51、数据库如何保证原子性?
主要是利用 Innodb 的undo log。undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的 SQL语句,他需要记录你要回滚的相应日志信息。例如
- 当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据
- 当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作
- 当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作
undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
52、数据库如何保证持久性?
主要是利用Innodb的redo log。重写日志, 正如之前说的,MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再写回到磁盘上。如果此时突然宕机,内存中的数据就会丢失。怎么解决这个问题?简单啊,事务提交前直接把数据写入磁盘就行啊。这么做有什么问题?
- 只修改一个页面里的一个字节,就要将整个页面刷入磁盘,太浪费资源了。毕竟一个页面16kb大小,你只改其中一点点东西,就要将16kb的内容刷入磁盘,听着也不合理。
- 毕竟一个事务里的SQL可能牵涉到多个数据页的修改,而这些数据页可能不是相邻的,也就是属于随机IO。显然操作随机IO,速度会比较慢。
于是,决定采用redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。
采用redo log的好处?
其实好处就是将redo log进行刷盘比对数据页刷盘效率高,具体表现如下:
- redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
- redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
结语
你学废了吗