☕目录☕
🍚前言
🧀一、索引
🥣🥣1.1 概念
🥐🥐🥐1.4.1 查看索引
🍛🍛🍛1.4.2 创建索引
🍣🍣🍣1.4.3 删除索引
🍱二、事务
🍤🍤🍤2.3.1 原子性
🍲🍲🍲2.3.2 一致性
🍨🍨🍨2.3.3 持久性
🍗🍗🍗2.3.4 隔离性
🍰后续
前言
这一次,前面的 student表、course表、score表 、classes表 四张表 的数据没有删除~~
咱们现在可以接着用~~
虽然这一篇博客 八股文 所占的比例还是比较重的~~
在这一篇博客中,所要介绍的是 MySQL数据库中的 索引和事务~~
那么现在,正文开始 ......
一、索引
1.1 概念
如果说 "索引" 这个词语 对你们有一点抽象,那么 可以换一个词语来表达 —— "目录"~~
每一本书,都有自己的目录~~
想观看某一章节内容,翻开目录,它就会告诉你 该章节在多少页~~
这就比一页一页的翻 要高效率的多~~
目录 所存在的意义,就是在加快查找的速度~~
想要理解 索引,可以先来理解 目录~~
虽然说 目录是索引的一种,索引不是目录;
但是,可以 "以小见大",通过 学习目录 来学习索引~~
1.2 为什么要使用索引
说白了,索引 实质上是一张描述索引列的列值与原表中记录行之间一 一对应关系的有序表~~
在 MySQL 中,通常有以下两种方式访问数据库表的行数据:
1) 顺序访问
顺序访问是在表中实行全表扫描,从头到尾逐行遍历,直到在无序的行数据中找到符合条件的目标数据~~
顺序访问实现比较简单,但是当表中有大量数据的时候,需要从头到尾进行遍历,效率非常低下~~2) 索引访问
索引访问是通过 遍历索引 来直接访问表中记录行的方式~~
使用这种方式的前提是 对表建立一个索引,在列上创建了索引之后,查找数据时可以直接根据该列上的索引找到对应记录行的位置,从而快捷地查找到数据~~
比如,在 student表 中,如果基于 student_id 建立了索引,系统就建立了一张 索引列到实际记录的映射表~~当用户需要查找 student_id 的相关数据时,系统先在 student_id 索引上找到该记录,然后通过映射表直接找到数据行,并且返回该行数据~~
可以这样理解,一个表里面有很多列,一列数据所占用的空间比较大,只能放在磁盘上~~
做索引 常见的情况就是 拿其中的某一个 id 来做索引,单拎出来的 id 可是要比整个行都小多了~~
所以 扫描索引的速度一般远远大于扫描实际数据行的速度,即 采用索引的方式可以大大提高数据库的工作效率~~
简而言之,不使用索引,MySQL 就必须从第一条记录开始读完整个表,直到找出相关的行。表越大,查询数据所花费的时间就越多。如果表中查询的列有一个索引,MySQL 就能快速到达一个位置去搜索数据文件,而不必查看所有数据,这样将会节省很大一部分时间~~
1.3 索引的使用场景
"索引" 本质上是通过一些更复杂的数据结构,把所有待查询的记录给组织起来了,从而就能够加快查找的速度!!!
(1)消耗了额外的空间!!!
这一点 可以类似于新华字典,或者是写博客之类的:
新华字典的目录 需要耗费纸张吧,写博客如果加上目录 需要耗费一定的空间吧~~
(2)有了索引,可以加快 查询的速度,但是拖慢了 增删改的速度!!!
这一点 可以类比于目录,如果 张三同学 写了一本书,此时 书的内容已经写好了,并且目录已经写好了;但是,此时 张三同学 突然又想到了 某些好点子,或者又觉得书上的某些内容不好,或者是又想修改某一部分内容~~
那么,此时 张三同学 不仅仅是要修改书的内容了,书的目录也是要修改的,那肯定是拖慢了速度~~
即 索引也不是尽善尽美的,主要是看场景是否合适:
- 对于空间不紧张,对于时间更敏感,可以使用 索引(以空间换时间)~~
- 查询频繁,增删改不频繁,可以使用索引(其实,这个也是大多数的情况,比如说 教务系统)~~
所以说,满足了上述条件,就可以考虑 对表中的字段 创建索引,以提高查询效率~~
反之,如果非条件查询列,或经常做插入、修改操作,或磁盘空间不足时,不考虑创建索引~~
1.4 索引的使用
1.4.1 查看索引
show index from 表名;
虽然后面有许多,但是 现在主要关注的是 Key_name 和 Colum_name:
一开始,咋们是没有创建索引的~~
但是,虽然前面没有给这个 student表 手动创建索引,
却可以看到它自带了一个索引~~
我们需要注意的是,
一个表的主键列会自动带上索引,unique以及外键约束的列,也会自动带上索引~~
像 主键 这种,需要保证 记录不重复,
每次插入新纪录,都需要查询一下旧记录,看看新记录是否已经存在,判断是否重复,插入是否成功~~
此时,就需要进行频繁的查询~~
当然,unique 和 外键约束 也是如此,需要进行频繁的查询~~
1.4.2 创建索引
create index 索引名 on 表名(列名);
我们可以创建索引~~
先看看 student表 的结构:
现在,我们可以创建一个索引:
现在,我们就可以查看刚刚创建的索引:
1.4.3 删除索引
drop index 索引名 on 表名;
此时,我们可以删除 刚刚创建的 idx_student_name索引:
此时,索引删除成功~~
创建索引 和 删除索引,也是危险操作!!!
尤其是针对一个已经包含很大数据量的表 进行操作的时候!!!
如果是针对一个大表,创建索引,就会导致大规模磁盘IO,直接把主机的磁盘IO吃满!!!
主机就卡了,无法对线上服务进行响应~~
二、事务
2.1 为什么使用事务
在实际操作中,有些操作,希望是一个整体~~
假设,张三同学 谈了一个对象,那么 我们把中间某些流程简化一下,张三同学 肯定是希望把 给彩礼 => 领证 => 办酒席 这些环节 希望是一个整体~~
如果 中间的某些环节出现了问题,那么 希望能够把这些问题的影响降到最低~~
如果彩礼给完了,领证的时候,女方发现张三同学是一个渣男,后悔了 不想结婚了~~
女方想要反悔就得退回彩礼~~
如果彩礼给完了,证也领了,想反悔~~
那就需要先把彩礼退了,然后还得去民政局办个离婚证~~
这三个操作希望能够一气呵成,当做一个整体来进行!!!
如果执行中间过程中,出现异常,就需要把前面已经进行过的操作 进行回退/恢复~~
恢复成好像完全没有操作过的样子~~
此时,把这三个环节(一个整体),就可以视为一个 "事务"!!!
2.2 事务的概念
事务就是把多个步骤,多个操作,打包成一个步骤,一个操作~~
其中任意一个步骤执行失败,都会进行回退,使这里的影响被降到最低~~
2.3 事务的特性
2.3.1 原子性
在数据库上涉及到很多和事务相关的操作!!!
最典型的:转账~~
李四 给 王五 转账转500,
涉及到的操作:
- 给 李四 -500 => SQL1
- 给 王五 +500 => SQL2
如果在执行SQL1之后,再执行SQL2的过程中,数据库出现问题了(宕机了),那么 李四就会减少500,而王五却并没有增加500,显然这个是非常不科学的~~
按正常的情况,要么两个都执行完,要么就一个都不执行!!!
这个就是事务最核心的特性:原子性!
(事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据改操作要全部执行,要么全部不执行;这种特性称为原子性 —— 是事务诞生的初衷)~~
注意:此处的 "一个都不执行",不是真的没执行~
而是通过恢复的方式,把之前造成的影响给还原了~~
这个还原的过程,称为 "回滚"(rollback)~~
数据库的事务回滚,是如何做到的?
数据库里的每个操作,在内部都有记录~~
尤其是事务内部的操作~~
如果事务中间出现问题,就可以根据之前的记录,来进行恢复了~~
2.3.2 一致性
事务的一致性,指在一个事务执行之前和执行之后数据库都必须处于一致性状态~~
这种特性称为事务的一致性~~
简单的来说,就是 一致数据是对的,没有纰漏~~
就比如上面的转账的例子,李四 给 王五 转账转500,李四 -500,那么王五就得要 +500;不能说 李四 -500 而王五 +400 ~~
2.3.3 持久性
持久性,即 存储在磁盘上,事务进行的操作都会写在磁盘上,只要事务执行成功,造成的修改,就是永久化保存的了;哪怕重启主机,这样的改变也存在~~
磁盘严格来说和硬盘不是一个东西~~
虽然都是外存~~
但是此处也就直接混用了~~
2.3.4 隔离性
事务的隔离性:这个是 描述多个事务并发执行的时候,所产生的情况~~
正好 现在正处于疫情期间,相信大家对于 "隔离" 这个词语已经很熟悉了~~
其实,事务的隔离性 和 现在疫情的隔离意思差不多的~~
疫情的隔离,有几种级别:
- 定点医院隔离(隔离效果最好,出现交叉感染的概率最小,成本最高!!!)
- 指定酒店隔离(隔离效果比较好,成本比较高)
- 居家隔离/在学校隔离(隔离效果一般,成本比较低)
- 不隔离 (没啥隔离效果)
根据不同的隔离级别,严格情况不同,所以效果也是不同的~~
隔离的目的是:为了避免相互之间产生影响~~
而数据库中的事务 彼此之间也是可能会相互影响的,
事务的隔离性也就是在描述事务执行过程中,影响能接受到啥程度~~
如果事务是一个一个执行的,那么还好;
但是如果事务是并发执行的,那么相互之间可能会造成影响~~
并发执行,可以简单粗暴的理解成,两个事物在同时执行!!!
在之前介绍过,一个数据库服务器 可以给多个客户端提供服务,
这个时候就可能会涉及到 说多个客户端同时尝试操作一个表,
就可能会产生这种并发事务的情况~~
比如说,现在这里有一张售票表(剩余票数1),
现在 客户端可能要执行 "买票事务":
- 先检查剩余的票数是否 > 0
- 如果 > 0 就修改收票表里的计数,让计数 -1
如果两个客户端不加限制,同时进行,那么就会带来麻烦:
客户端1 和 客户端2 都执行了 -1 操作,那么 实际上票数就变成了 -1~~
这种情况叫做 "超卖"!!!
上述问题,就是两个事务之间产生了干扰和影响,
为了避免相互干扰,就引入了 "隔离性",通过隔离性来降低上述的影响~~
为啥要并发执行?
目标是为了提高执行效率!!!
提高隔离性,带来的问题就是 数据更准确了,但是效率更低了~~
为了解决并发执行事务带来的问题,MySQL等数据库引入了 "隔离级别",可以让用户自行选择一个 适合自己当前业务场景的级别~~
先研究一下,并发执行事务的时候 还会有哪些问题?
(1)脏读问题(数据被污染了,不准了)
举个例子:
张三同学 在写代码,写了一个 Student类,写了一些属性和方法~~
在他写的过程中,李四走过来看了一会儿,就走了~~
在李四走了之后,张三同学 又把 Student类 里的实现又改了~~
此时,李四 读到的数据就是 "脏数据",上述的观察过程,就是 "脏读"~~
用数据库的术语来说,
一个事务 A,在执行过程当中,对数据进行了一系列的修改。在提交到数据库之前(完成事务之前),另一个事务 B,读取了对应的数据;
此时 这个 B 读到的数据都是一些临时结果,后续可能马上就被 A 给改了,此时 B 的读取行为就是 "脏读"~~
这个问题好解决:
张三同学 和 李四 约定好,在提交到数据库之前,李四不要去看该代码,等到提交到数据库之后,再去看看!!!
即 给读操作加锁(提交之前 不允许随便去读了啊,如果要想读,那就要去向 张三同学/A 申请)~~
相当于降低了并发程度,降低了效率,提高了隔离性~~
(2)不可重复读
即 事务A提交了之后,事务B才开始读(读是加锁了),然后在B的执行过程中,A又开始了一次,修改了数据;
此时,B执行中,两次读取操作,结果可能不一致,这个就叫做 "不可重复读"~~
如何解决上述问题:
来进行重新约定:
- 张三同学提交之前,李四不要去读(之前的加锁)
- 李四读的时候,张三同学不要去改代码
因此,隔离性又提高了,并发性又降低了,数据更准确了,效率又更低了~~
(3)幻读
闲着也是闲着,张三同学 在 李四 读代码的时候,又开始改别的代码;
李四 在读 student类的代码,张三就在改 teacher类的代码~~
但是,这里又出现了一个问题,如果 teacher类 本来就存在,也就罢了;
如果是在李四读的过程中,张三新增了一个 teacher类,或者是删除了一个已有的内容,就会产生 "幻读" 问题~~
事务B读取过程中,事务A进行了修改,没有直接修改B读取的数据,但是 却影响到了B读取的结果集~~
事务B两次读取到的结果集不一样~~
这个就叫做 "幻读",相当于是 "不可重复读"的一种特殊情况~~
解决 "幻读"问题,核心思路就是 "串行化",
严格要求,李四 在读代码的时候,张三不要去修改任何东西,保证读和写操作都是严格串行执行的(串行:一个执行完,才能执行另一个)~~
隔离性最高,并发程度最低,数据的准确性最好,同时效率最慢~~
为啥要并发 —— 效率!!!
为啥要隔离 —— 准确!!!
两者一般情况下是相悖的~~
实际使用的时候,就需要根据实际场景,来决定如何选择档位(看场景对于 性能 比较敏感,还是对于 准确性 比较敏感) ~~
如:转账操作,一分钱都不能差,慢点没事,一定要稳~~
B站之类的,大博主 浏览数/点赞数......这些对于准确性不是很高~~
MySQL 里提供了 4 个档位,可供自由选择:
- read uncommitted 并发能力最强,隔离性最弱
- read committed 只能读取提交之后的数据,解决了 "脏读"问题
- repeatable read 针对读和写都限制了,解决了不可重复读问题
- serializable 严格的串行执行,解决了 "幻读"问题
1 —> 4,并发能力逐渐减弱,隔离性逐渐增强~~
我们可以根据实际需要,在配置文件里面,修改数据库的隔离级别(配置文件是之前演示过的 MySQL修改字符集的那个 my.ini 里)~~
默认的是第3个隔离级别,解决了 "不可重复读"问题~~
2.4 事务的使用
- 开启事务:start transaction;
- 执行多条SQL语句
-
回滚或提交: rollback/commit;
在实际开发的时候,很少通过SQL代码来写事务,更多的是借助其他编程语言~~
后续
这就是MySQL数据库 索引和事务的内容啦~~
下一篇就是 MySQL数据库 JDBC编程 部分的内容了~~
如果这篇博客给你带来了收获~~
可以留下一颗小小的赞吗~~