目录
1. 索引是什么
2. 索引的相关操作
3. 索引的原理
4. 事务是什么
5. 事务的使用
6. 事务的原理
1. 索引是什么
索引是用来加快查询的机制,是针对某个表的指定列来设置的,查询条件如果就是使用这个带有索引的列来查询,那么查询速度就会加快。
数据库在使用 select 查询的时候:
1. 先遍历表
2. 把当前的行给带入到条件中,看条件是否成立
3. 条件成立,这样的行就保留,不成立就跳过
如果表非常大,遍历成本就非常高了,至少是 O(N)
而数据库是把数据存储在硬盘上的,每次读取一个数据就都需要读取硬盘,而读取硬盘跟读取内存比,是非常慢的,慢上加慢,那就很难受了。
索引,就是针对 查询 操作引入的优化手段,通过索引,就能加快查询的速度,避免针对表进行遍历。
可以把索引当作一本书的目录,通过目录,就能快速查找到哪个章节在哪一页从而阅读对应的内容,而通过索引,就能加快查询的速度,快速的找到对应的行记录。
索引是能提高查询速度,但是也有代价:
1. 占用更多的空间
生成索引,是需要一系列的数据结构,以及一系列的额外的数据,来存储到硬盘空间中的。
2. 可能会降低插入查询修改的速度
删除数据,也需要将索引里对应的内容给删除。
实际开发中,查询(读)的频率,比插入删除修改(写)的频率高很多,所以索引是非常有用的。
2. 索引的相关操作
在有 主键/外键/unique 约束的情况下,会自动生成索引。
1) 查看索引
show index from 表名;
一本书可以有多个目录,一个表也可以有多个索引。
2) 创建索引
create index 索引名字 on 表名(列名);
创建索引是一个危险的操作。
创建索引的时候,需要针对现有的数据,进行大规模的重新整理。
如果当前表是一个空表,或者数据不多,那么创建索引都没事,
如果这个表很大,创建索引,就容易把数据库服务器给卡住。
如果确实想要创建索引的话,就需要使用其他的技巧。
比如:可以另外再搞一个机器,部署 mysql 服务器,也创建同样的表,并且把表上的索引提前创建好,再把之前机器上的数据,给导入到新的 mysql 服务器上(导入数据的过程,就可以控制节奏),多花点时间导数据都没事,但是不要影响到原来服务器的正常的运转,当所有的数据导入完毕,就可以用新的数据库替换旧的数据库了(移花接木)。
3) 删除索引
drop index 索引名 on 表名;
手动创建的索引,可以手动删除,但是自动生成的索引(主键/外键/unique),是不能删除的。
删除自动生成的索引,就会报错。
删除索引操作也是一个危险操作。
3. 索引的原理
索引也是通过一定的数据结构来实现的。
数据库引入的索引是一个改进的数据结构,B+ 树 (N 叉搜索树)
为了了解 B+ 树,就需要先了解 B 树。
B 树是一个 N 叉搜索树,每个节点上有 M 个 key,划分出 M + 1 个区间。
进行查询的时候,就可以直接从根节点出发,判定当前要查的数据在节点的哪个区间里,再来决定下一步往哪里走。
进行添加/删除元素的时候,可能会涉及到 节点的拆分 和 节点的合并 。
B+ 树,是相对于 B 树,又做出了一定的改进,这个改进,可以说是为数据库量身定做的。
B+ 树特点:
1) B+ 树也是一个 N 叉搜索树,一个节点上存在 N 个 key,划分出 N 个区间。
2) 每个节点的 N 个 key 中,最后一个,就相当于当前子树的最大值。
3) 父节点上的每个 key 都会以最大值的身份在子节点的对应区间中存在 (key 可能会重复出现),这就使得,叶子节点这一层,包含了整个树的数据全集。
4) B+ 树会使用 链表 这样的结构,把叶子节点串起来。
此时就可以非常方便的完成数据集合的遍历,也可以非常方便的从数据集合中按照范围取出一个 "子集" 来。
B+ 树的优点(相对于 B 树,哈希表,红黑树):
1. N 叉搜索树,树的高度是有限的。降低 IO 的次数。
2. 非常擅长范围查询。
3. 所有的查询最终都是要落到叶子节点。
查询和查询之间的时间开销是稳定的,不会出现这次特别快,下次特别慢的情况。
4. 由于叶子节点是全集,所以会把行数据存储在叶子节点上,非叶子节点只是存储一个用来排序的 key (比如存个 id )
数据库是按行组织数据的,创建索引的时候,是针对这一列进行创建的。这一行数据,内容是比较多的,而这一个 id,内容是比较少的,叶子节点会非常占用空间,而非叶子节点则占不了多少空间,所以就可以直接把非叶子节点缓存到内存空间中硬盘中还是要存储非叶子节点的,但是当我们进行查询的时候,就可以把这些非叶子节点加载到内存中,整体查询的比较过程就可以在内存中进行了,又进一步的减少了 IO 访问次数)。
mysql 的索引实现,也是有一些变数的,不是只有 B+ 树一种情况。
mysql 内部有个模块,存储引擎。
存储引擎是提供很多版本的实现的,当前最主要使用的存储引擎是 Innodb,Innodb 使用的就是 B+ 树。
4. 事务是什么
开发中经常会涉及到一些场景,需要 "一气呵成" 的完成一些工作。
比如说转账:
引入事务,就是为了避免上述问题。
事务就可以把多个 sql 打包成一个整体,可以保证这些 sql 要么全都执行正确,要么 "一个都不执行" (不是真的一个都没执行,必然得先执行了,才知道成没成功,只是看起来一个都不执行一样)。
事务把这多个 sql 打包到一起,作为一个整体来执行,这样的特点,称为 "原子性"(不可拆分的)。
如果在事务中的 sql 有某一条执行失败了,那就会进行回滚,将状态恢复到执行事务之前。
5. 事务的使用
单独执行的每个 sql,都是自成一个体系,此时这些 sql 之间是没有原子性的。
1) 开启事务:
start transaction;
开启事务之后,就可以执行各种 sql 了,此时这些 sql 都会别当成一个原子来执行。
2) 提交事务/事务回滚
提交事务:
commit;
事务结束。
事务回滚:
rollback;
主动触发回滚。
出错有很多种情况情况,崩溃只是其中一种。
rollback 一般是要搭配条件判断来使用的,一般是搭配其他的编程语言来使用 rollback。
6. 事务的原理
1) 回滚是怎么做到的?
通过日志的方式,来记录事务中的关键操作,这样的记录,就是回滚的依据。
日志就是打印出来的内容,一般会把日志放到文件里,即使是主机掉电,也不影响,因为回滚用的日志已经在文件中了,一旦重新启动主机,mysql 也重新启动,就会发现回滚日志中有一些需要回滚的操作,于是就可以完成回滚了。
2) 事务的特性:
1. 原子性
通过回滚的操作,保证这一系列操作,都能执行正确,或者恢复如初。
2. 一致性
事务执行之前,执行之后,数据都不能离谱。
一致性很多时候是要靠数据库的约束以及一系列的检查机制来完成的。
3. 持久性
事务做出的修改,都是在硬盘上持久保存的,重启服务器,数据仍然是存在的,事务执行的修改仍然是有效的。
4. 隔离性
隔离性是数据库并发执行多个事务的时候,涉及到的问题。
一个服务器可以给多个客户端提供服务,多个客户端都会让数据库执行事务,很有可能客户端1 提交的事务1,执行了一半,客户端2 提交的事务2 也过来了,此时数据库服务器就需要同时处理(并发执行)这两个事务。所以并发程度越高,效率也就越高。
所以如果希望数据库执行效率高的话,就需要提高并发程度。
但是在并发执行的过程中,可能会出现一些问题,导致数据就出现一些 "错误" 的情况。
在并发执行的过程中,会出现以下问题:
1. 脏读
一个事务 A 在写数据的时候,另一个事务 B 读取了同一个数据。
接下来事务 A 又修改了数据,导致之前 B 读的数据是无效的数据/过时的数据 (脏数据)。
举个例子:我在写博客的时候,我的室友就在我身后看我写博客,然后在我博客写到一半的时候,室友有事要去做,就走了,后面我又觉得前面的博客写得不是很好,所以直接大改前面的内容,改的面目全非,跟原来的那一版差别很大,后面我发布博客,室友在网上看我写的博客,就发现这跟之前写得区别很大。
这种情况就是脏读,我是事务A,负责写,室友是事务B,负责读,但是我又修改了写过的数据,导致室友之前读的数据都是无效的数据/过时的数据。
如果两个事务一个接一个的串行执行,就没问题,如果是并发执行,就容易出问题。
解决脏读问题的核心思路是,针对写加锁。(我和室友约定好,我写博客的时候,你不要读)
等到我写完了,不修改了,将博客发布到网上,这时室友再来读博客就没问题了。
约定了写加锁之后,并发性降低了,效率也降低了,但是数据准确性提高了,隔离性也提高了。
2. 不可重复读
并发执行事务过程中,如果事务 A 在内部多次读取同一个数据的时候,出现不同的情况,这种就是不可重复读。事务 A 在两次读之间,有一个事务 B 修改了数据并提交了事务。
还是刚刚那个例子,我还是写博客,约定了写加锁(跟室友说我写的时候,你别看我屏幕),等我写完博客之后,直接发布。然后我的室友就来看我写的博客,我想了想,还是觉得刚刚写的博客不太满意,就想把博客优化一下(我改博客的时候,室友并不能感知到),但是当我修改完之后,再次发布(假设不考虑审核时间的问题),室友就发现,这博客内容跟她刚刚看的不一样啊。
这种情况就是不可重复读问题,室友是在读我博客的时候,我又修改了同一篇博客的内容,导致她看到的博客内容不一样。
刚才写加锁的时候,只是要求我读的时候,室友不能看我屏幕,在我写的时候不能读,但是没说在室友读的时候我不能继续写。
解决不可重复读的关键是:给读操作加锁。(我跟室友约定好,在她读我博客的时候,我不能修改这篇博客内容)
约定读加锁之后,并发程度又降低了,效率又降低了,数据准确性进一步提高了,隔离性也进一步提高了。
3. 幻读
一个事务 A 执行过程中,两次的读取操作,数据的内容没变,但是结果集变了。
约定了写加锁和读加锁,我在写的时候,室友不能读,只能读我发布后的博客,室友在读的时候我不能写,此时就已经解决了脏读和不可重复读问题。
虽然约定了读的时候不能写,约定的是使用在读博客1 的时候,我不能修改博客1 内容,虽然我不可以写博客1 ,但是我可以写博客2 呀(在室友读的时候,我也可以干点其他的,不至于完全闲着)。此时,虽然我没有修改博客1 ,但是室友突然发现,网站上又突然出现了一篇我写的博客2 。
解决幻读的关键是:串行化执行,保持绝对的串行执行事务,此时完全没有并发,效率最低,隔离性最高,准确性最高。
串行化就是从根本上解决了并发涉及到的问题。
隔离级别,就是在 "数据正确" 和 "效率" 之间做平衡。
往往提升了 "效率",就会牺牲 "正确性",提升 "正确性",就会降低 "效率"。
不同的需求场景就有不同的要求。
有的场景追求正确性,效率是其次:比如转账,和钱打交道之类的场景。
有的场景追求效率,正确性是其次:比如微博点赞...
mysql 服务器也提供了 "隔离级别" 让我们针对隔离程度进行设置,可以在 mysql 的配置文件中修改。
mysql 的隔离级别:
mysql 默认的隔离级别是 repeatable read,可重复读。