꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱
ʕ̯•͡˔•̯᷅ʔ大家好,我是xiaoxie.希望你看完之后,有不足之处请多多谅解,让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客
本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶
个人主页:xiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客系列专栏:xiaoxie的MySql学习系列专栏——CSDN博客●'ᴗ'σσணღ
我的目标:"团团等我💪( ◡̀_◡́ ҂)"( ⸝⸝⸝›ᴥ‹⸝⸝⸝ )欢迎各位→点赞👍 + 收藏⭐️ + 留言📝+关注(互三必回)!
索引和事务,在日后面试中,算是比较高频的考点,在这里博主总结了一些关于索引和事务的重点和要点
目录
一.索引
1.1 概念
1.2作用
1.3核心内容
1.索引有什么作用?
2.索引背后的数据结构
1.以哈希表作为索引
2.以红黑树作为索引
3.b+树为什么适合作为索引
4.B+树相对于B树有以下不同之处
1.4索引查询过程
1.5索引的语法
1.6说明
二.事务
2.1事务的概念
2.2 为什么要使用事务
2.3 事务的流程
2.4面试题:事务的基本特性
1.原子性(Atomicity:
2.一致性(Consistency)
3.持久性
4.隔离性(考察的重点,可以说主要就是考察隔离性)
1.脏读问题
2.不可重复读问题
3.幻读问题
4.总结
一.索引
1.1 概念
索引是一种特殊的文件,包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引, 并指定索引的类型,各类索引有各自的数据结构实现。
1.2作用
1.数据库中的表、数据、索引之间的关系,类似于书架上的图书、书籍内容和书籍目录的关系。
2.索引所起的作用类似书籍目录,可用于快速定位、检索数据。
3.索引对于提高数据库的性能有很大的帮助
1.3核心内容
1.索引有什么作用?
我们都知道mysql查找主要是select同时数据库应用最多的也是select,
select基本执行过程,遍历表,依次取出每个记录,根据where字句的条件,决定这个记录要保留还是过滤,像这样的遍历操作,本身是比较低效的(尤其是数据量很大的时候)
为什么特别低效呢?因为把数据存储在硬盘上的,取出每个记录(这个操作都意味访问硬盘) 相比之下,我们更希望访问硬盘的次数尽量少。
索引的作用就在这里了,通过索引背后的数据结构,加快查询表的速度,减少表的遍历,而索引的核心内容就是,其背后的数据结构,
2.索引背后的数据结构
大家都知道,提供查询速度的数据结构有很多,例如其中最典型的哈希表查询的时间复杂度一般我们认为O(1),红黑树查询的时间复杂度为O(logn),这两个都是很好的可以减少查询的数据结构,但为什么索引用的是数据结构为B+树呢,(这是很关键的问题,面试关于Mysql方面的知识很喜欢问这个)
1.以哈希表作为索引
我们以 key-value 数据的索引为基础,假设数据存储全部采用追加式的文件组成,那么最简单的索引策略,莫过于把每个 key 对应文件中的字节偏移量(也就是在文件中的位置),保存到内存中的哈希表中(Hashtable 或 HashMap),这样就可以快速找到每个值的位置。
每当向数据库追加新的 key-value 时,也就需要更新内存中的 HashMap 来记录刚刚写入数据的偏移量;当查找某个值时,使用 HashMap 来找到文件中的存储位置,然后读取其内容。
上面的方式虽然很简单,但是的确是一个可行的方法。静下来思考,多数实现其实都是换汤不换药,变化无非也就是通过计算 key 的 hash 值,然后映射对应的 value。
但是哈希索引使用起来,存在一定的问题(注意,这块面试的时候经常会聊呦!)。
-
哈希表必须全部放入内存,如果有大量的 key 的情况下,表现就不会那么好啦;
-
区间查询效率不太好,例如要查询 key0000 和 key9999 区间的所有键,只能采用逐个查找的方式查询每一个键(这是关键,因为我们平常使用数据库时经常会涉及、>、>=、<、<=、 或 BETWEEN 运算符使用哈希索引就不可以满足到我们的需求的);
-
由于哈希索引数据没有按照索引值顺序存储,所以无法用来进行排序。
2.以红黑树作为索引
大家都知道红黑树本质上是一颗二叉排序树树,那么它就可以进行范围查询,并且查询速度也还可以为O(logN)但为什么我们不用红黑树呢,主要是以下几点原因:
-
范围查询效率高:红黑树在范围查询时需要进行中序遍历,如果节点数量多(一般来说在真正的开发环境里,数据量至少都是百万基本的)效率极低。
-
磁盘I/O多:红黑树在每个节点都需要存储数据还是相同的节点数量多的话,占用空间较大,并且每次查询磁盘I/O多.
-
结构不稳定:红黑树在插入和删除操作时会进行频繁的旋转和调整,这会导致索引的维护成本较高。对于索引来说,我们更希望能够快速地进行查找操作,而不需要频繁地调整树的结构.
3.b+树为什么适合作为索引
假设我们有这样一张表:
create table student(id int paimary key,
name varchar(20),age int,gender varchar(2),height int....);
以id作为索引,b+树是这样,组织数据的:
关于B+如何插入数据,删除数据什么的这里博主就不过多的解释,只说明关于Mysql中的内容
1.非叶子节点的值都会在其子节点上重复出现,这样做是为了,最后,叶子节点这一层就包含了整个数据集合
2.非叶子节点只存id,占用的空间就比较小(这样就可以把非叶子节点缓存到内存中,当然这些数据也同时保存在硬盘上) 这样查询速度就加快了(减少磁盘I/O操作),而叶子节点储存的就不仅仅是id,还有name,age,gender等等数据,这样每次查询到最后都是最终要在叶子节点上完成,这样查询的磁盘I/O时间是基本相同,比较稳定
3.叶子节点最终是通过类似于链表的数据结构连接在一起,这样就遍历整张表的数据,以及进行范围比较,区间查询都是非常方便的
4.B+树是一种多路搜索树(减少了树的高度),能够快速定位到目标数据,因此非常适合作为索引结构
4.B+树相对于B树有以下不同之处
大家都知道还有一种树叫做B树也是多路搜索树,那为什么我们要用B+树作为索引呢,这里就不展示B树了,大家感兴趣的话可以自己查查百度
-
更大的磁盘空间利用率:
- B+树的内部节点(非叶子节点)不存储数据,仅存储索引,这使得单个节点能够容纳更多的键值,从而降低树的高度,特别是在大数据集下,B+树的整体高度通常小于相同阶数的B树,减少了磁盘I/O操作的数量。
-
更好的顺序访问效率:
- 所有数据都存储在叶子节点,并且叶子节点之间通过指针相连,形成一个有序链表。这样的设计使得进行范围查询或全表扫描时非常高效,只需遍历叶子节点链表即可,无需像B树那样深入多个层级或者跳转回父节点。
-
稳定的查询性能:
- 在B+树中,任何查询无论是否包含键值都需要查找到叶子节点,这意味着所有查询都有相同的路径长度,提供了更稳定的查询性能。
-
减少内存需求:
- B+树内部节点不存储实际数据,故内存缓冲区可以加载更多的内部节点,增加索引的覆盖范围,从而提高索引效率。
综上所述,B+树更适合于大量数据存储以及大型数据库系统,尤其是在需要频繁执行范围查询和顺序访问的场景中表现更为优秀。而B树虽然在某些情况下(比如数据离根节点近并且访问频繁时)可能有更快的随机查找速度,但在大规模数据管理和磁盘I/O优化方面,B+树具有明显优势。
1.4索引查询过程
这里先总结一些关于索引的知识,再说明索引查询过程
总结:
1.B+树存在的前提
B+树存在的前提是使用了InnoDB这个储存引擎,MySQL数据库提供了多种存储引擎,不同的存储引擎使用的索引的数据结构也是不同的,而InnoDB是最常用也是面试最常问的
2.B+树是一直存在的
如果你创定义了主键,那么就按照主键来创建,如果你没有定义主键,那么就会有自带的隐藏列来建立
3.有主键就一定有索引吗
在MySQL以及其他大多数关系型数据库系统中,如果定义了一个字段或一组字段为主键,那么数据库会自动为此主键创建一个索引。主键实际上是一种特殊的唯一索引,它不仅确保了数据的唯一性,还为数据库提供了高效的查询机制,因为它会自动被索引,使得通过主键值可以快速定位到相应的行记录。
索引查询过程:
假设我们有这样一张表
create table student(id int paimary key,
name varchar(20),age int,gender varchar(2),height int....);
1.查询主键索引的过程:
当执行查询如 select * from student where id = 1;
时,MySQL首先会检查查询条件是否涉及到主键id
。如果是,它将直接在主键索引(通常为B+树)中查找id
为1的记录。主键索引的叶节点包含了完整的行数据(在InnoDB引擎中表现为聚簇索引,数据行跟索引在一起),因此找到索引后就能立即获取所需的所有列数据,无需额外的回表操作。
2.查询非主键非索引的过程:
当执行查询如select * from student where age = 18;
由于数据库中并没有关于
age的索引,
MySQL必须进行全表扫描,逐行对比age
的值,直到找到匹配项为止。这个过程对于大表来说效率较低,因为可能需要遍历整个表的数据.
3.查询非主键索引的过程
假设我们为name建立索引该字段是非主键但是已建立索引,当执行查询如 select id from student where name = "张三";首先在name对应的非主键索引(二级索引)中定位到name =
的"张三"索引记录,如果只是查询id索引覆盖的列,则可以直接从索引中取得数据,无需回表若查询包括不在索引中的其他列如select * from student where name = "张三",则需要通过索引记录中存储的主键值,再去主键索引中查询对应的完整行数据,这个过程称为“回表”
总结起来,主键索引查询最快,直接命中索引并获取数据;非主键索引查询能利用索引加速查询,但可能涉及回表;非索引字段查询最慢,一般需要全表扫描。
1.5索引的语法
create [unique | fulltext] index 索引名
on 表名 (列名1, 列名2, ...);
-- 或者对于唯一索引
create unique index index_name
on table_name (column1);
-- 或者对于全文索引(mysql 5.6及以上版本对innodb支持全文索引)
create fulltext index index_name
on table_name (column1);
-- 创建复合索引(多个字段组合)
create index index_name
on table_name (column1, column2);
-- 创建主键索引(主键自动成为唯一索引)
alter table table_name
add primary key (column1);
-- 删除索引
drop index index_name on table_name;
1.6说明
这里还需要提醒大家一点,索引虽然很好用,可以加快查询速度但并不是越多越好,主要原因如下:
-
存储空间消耗:
每创建一个索引,数据库都需要额外存储索引数据,尤其是对于大型数据库,索引可能占用大量的存储空间,这会增加数据库的存储成本。 -
写操作性能损耗:
更新、插入或删除数据时,除了修改数据表本身,还需要同步更新所有相关的索引。当存在大量索引时,写操作的开销会显著增大,尤其是在高并发写入的场景下,频繁的索引更新会严重影响性能。 -
维护成本增加:
索引需要维护和更新,随着索引数量的增长,数据库系统在后台进行的索引维护工作(如重建索引、合并索引碎片等)所需的资源和时间也会相应增加。 -
查询优化器决策复杂度:
当索引过多时,查询优化器在选择最优执行计划时可能面临更复杂的决策过程,有时可能反而会选择不到最佳的索引。 -
过度索引:
创建了不必要的索引,即那些很少被查询引用或者索引收益不大的列上的索引,会造成资源浪费,并且可能对查询性能并无实质帮助。 -
索引选择冲突:
若存在大量索引,尤其是复合索引,查询优化器可能无法精确判断使用哪个索引最合适,有时候可能不得不选择全表扫描而不是使用某个特定的索引。
因此,在设计数据库时,应合理评估每个索引的价值,只创建真正能提高查询性能且值得维护的索引,避免无谓的资源消耗。通过定期审查和调优,确保索引结构与实际业务需求相匹配,这样才能最大化数据库的性能。
二.事务
2.1事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。 在不同的环境中,都可以有事务。对应在数据库中,就是数据库事务。
2.2 为什么要使用事务
这里博主举个小例子,假如在银行系统中,A给B转账,那么A的金额要减少,B的金额要增加,如果数据库突然挂了,就有可能出现A的金额要减少,B的金额却没有增加的情况,这个后果就是十分严重的,(例如上述例子,当涉及到多个SQL语句配合,完成某个操作时)我们就需要使用到事务这个机制,来确保操作的各个单元,要么全部成功,要么全部失败,不可能只完成一部分.
2.3 事务的流程
-
事务开始(Begin Transaction)
- 开启一个新的事务,此时系统进入事务模式。在编程接口中,可以通过诸如
START TRANSACTION
、BEGIN
等命令显式地启动一个事务。
- 开启一个新的事务,此时系统进入事务模式。在编程接口中,可以通过诸如
-
事务处理(Transaction Processing)
- 在事务内执行一系列数据库操作,如插入、更新、删除等。这些操作在事务期间被视为一个不可分割的工作单元。
-
事务的中间状态(Intermediate States)
- 在事务处理过程中,系统可能维持事务的中间状态,如果这时发生错误或需要回滚至某个点,可以通过
ROLLBACK TO SAVEPOINT
命令回到指定的保存点。
- 在事务处理过程中,系统可能维持事务的中间状态,如果这时发生错误或需要回滚至某个点,可以通过
-
事务的验证与提交(Validation and Commit)
- 当事务内的所有操作都成功执行完毕,并且满足业务规则和完整性约束时,可以尝试提交事务,通过
COMMIT
命令使事务中的所有变更永久生效。在提交前,系统会进行一系列的检查以确保事务的一致性。
- 当事务内的所有操作都成功执行完毕,并且满足业务规则和完整性约束时,可以尝试提交事务,通过
-
事务的回滚(Rollback)
- 如果在事务处理过程中出现错误、违反了完整性约束或应用程序决定撤销事务,可以使用
ROLLBACK
命令取消事务中的一切操作,使数据库返回到事务开始前的状态。 - 这里所说的回滚是指数据库在执行事务时,会将数据记录到日志中(存在硬盘中)如果在执行事务的过程中发生了意外,数据库就会读取硬盘中的日志的内容,来进行恢复操作
- 如果在事务处理过程中出现错误、违反了完整性约束或应用程序决定撤销事务,可以使用
-
并发控制(Concurrency Control)
- 在多用户环境下,数据库系统会通过锁机制、MVCC(多版本并发控制)等技术来保证事务的隔离性,防止并发事务间的相互干扰。
-
事务结束(End of Transaction)
- 无论事务最终是提交还是回滚,事务结束后,系统释放由该事务持有的资源,如解锁等,并准备接受新的事务请求。
总的来说,事务的生命周期就是从开始到结束的一个闭环过程,确保在这一过程中数据的完整性和一致性得到妥善维护。
2.4面试题:事务的基本特性
1.原子性(Atomicity:
原子性要求事务中的所有操作作为一个不可分割的整体来执行。事务中所有操作要么全部成功,要么全部失败,不存在部分成功的情况,并且有回滚机制,能够触发还原
2.一致性(Consistency)
事务在执行前后,数据库都必须保持一致性状态。这意味着事务的执行结果必须符合数据库的完整性约束和业务规则。即使事务执行过程中有多步操作,事务结束后,数据库也应该从一个有效状态转变为另一个有效状态。通俗一点来说就是:事务执行之前,和执行之后,保证数据是一致的,(就像对账一样,不会出现对不上账的情况)数据是同样要满足业务要求的.
3.持久性
一旦事务成功提交,其所做的修改将永久保存在数据库中(储存在硬盘上的),即便后续发生系统崩溃或者其他故障,已提交的事务数据也不会丢失。数据库通过redo日志和undo日志等机制保证事务的持久性。
4.隔离性(考察的重点,可以说主要就是考察隔离性)
1.脏读问题
脏读(Dirty Read)是在数据库并发控制中可能出现的一种不一致性问题,它发生在事务隔离级别较弱的情况下,具体指一个事务读取到了另一个事务尚未提交(Uncommitted)的数据。
例如,在事务A和事务B并发执行时:
- 事务A 开始执行,并修改了一条记录,但还没有提交这些更改。
- 事务B 在事务A还未提交时读取了这条被事务A修改但尚未提交的记录。
- 后续,事务A 由于某种原因选择了回滚(Rollback),撤销了对那条记录的所有更改。
- 此时,事务B先前读取到的数据实际上是无效的,因为它基于事务A未提交且最终被撤销的更改,这就是所谓的“脏读”。
为了避免脏读问题,数据库系统通常会提供不同的事务隔离级别,也就是写加锁如“读已提交(Read Committed)”隔离级别就可以避免脏读问题,在没有隔离的时候,事务A和事务B同时执行,而加了写加锁之后,事务A修改数据时对其加锁,直到事务A提交或回滚。在此期间,事务B试图读取事务A正在修改但尚未提交的数据时,事务B将不会读取到这些“脏”数据,而是等待事务A释放相关锁后才能读取到事务A已经提交的数据。此做法减低了"并发能力",增强了隔离性,提高了数据的准确性.
2.不可重复读问题
不可重复读(Non-repeatable Read)是数据库并发控制中另一种可能出现的一致性问题,它发生在事务隔离级别不足以避免此类问题的情况下。具体来说,不可重复读是指在一个事务内,同一个查询在不同的时间点执行,得到了不同的结果,这是因为在此期间其他事务对该数据进行了修改并提交。
例如,在两个并发事务A和B中:
- 事务A 开始执行,并读取了一条记录。
- 事务B 执行并修改了事务A刚刚读取过的那条记录,并提交了事务。
- 事务A 再次执行同样的查询去读取那条记录,却发现记录的内容已经发生了变化,与第一次读取的结果不同。
在“读已提交(Read Committed)”的事务隔离级别下,不可重复读的问题可能发生。为解决这个问题,数据库系统提供了更高的“可重复读(Repeatable Read)”隔离级别,也就是"读加锁"在这个级别下,事务在其整个生命周期内看到的数据始终是一致的,即事务开始时的数据视图在事务结束前不会改变,如事务A在开始时获得了一个快照(Snapshot),即事务A只能看到在它开始前就已经提交的数据版本。在事务A执行期间,即使事务B修改并提交了数据,事务A也不会看到这些新提交的变更,而是继续基于其自身的快照视图进行查询,因此可以避免不可重复读问题.这种做法进一步减低了"并发能力",进一步增强了隔离性,进一步提高了数据的准确性.
3.幻读问题
幻读(Phantom Read)是数据库并发控制中的另一类一致性问题,它发生在事务的“可重复读(Repeatable Read)”隔离级别下,即使在这个级别已经解决了不可重复读问题,但在某些情况下仍有可能出现。幻读特别体现在范围查询(Range Query)上,即当事务重新执行一个范围查询时,会发现有新的行(即“幻影行”)出现在范围内,这些行是事务开始之后由其他事务插入并提交的。
举例说明:
- 事务A 在“可重复读”隔离级别下开始执行,并执行了一个范围查询,比如
SELECT * FROM table WHERE age > 20
,得到N条记录。 - 事务B 插入了一条新记录,其age属性大于20,并提交了事务。
- 事务A 再次执行相同的范围查询,此时它会发现比之前多了至少一条记录(事务B刚插入的那条记录),尽管事务A在整个执行过程中没有更新任何数据,却观察到了新增的行记录,这就是幻读现象。
为了解决幻读问题,数据库系统提供了更高的事务隔离级别——“序列化(Serializable)”,在这个级别下,数据库系统会采用更强的并发控制策略,如更严格的封锁机制,使得事务执行如同单线程执行一样,一个事务完全结束并提交后,下一个事务才会开始,使得事务的执行看起来像是按照某种预定的顺序进行的,从而避免了幻读问题。然而,更高的隔离级别往往会带来更大的并发性能损失,.在MySQL的InnoDB存储引擎中,通过Next-Key Locks结合MVCC可以有效地解决幻读问题,但在某些特定条件下可能仍需用户手动进行锁升级以确保绝对的幻读防护,使用"序列化"也就意味着,几乎没有并发性,因为事务就相当于一条接一条的执行,不过牺牲了并发能力,带来的是更强的隔离性,以及更准确的数据.
4.总结
脏读问题(Dirty Read): 脏读是指一个事务读取到了另一个事务尚未提交的数据更改。在事务A未提交前,事务B读取了事务A修改过的数据,若事务A最终回滚,那么事务B读取的数据就是无效的。为了避免脏读,数据库系统提供了“读已提交(Read Committed)”隔离级别,在这个级别下,事务只能看到其他事务已经提交的数据。
不可重复读问题(Non-repeatable Read): 不可重复读是指在一个事务内,同一个查询在不同时刻返回了不同的结果,原因是其他事务在此期间提交了对数据的更改。在“读已提交”隔离级别下,不可重复读仍可能发生。为解决这个问题,数据库系统提供了“可重复读(Repeatable Read)”隔离级别,该级别确保事务在其整个生命周期内看到的数据视图始终保持一致,即事务开始时读取的数据在事务结束前不会发生变化。
幻读问题(Phantom Read): 幻读发生在“可重复读”隔离级别下,当事务重新执行一个范围查询时,发现有新的行(即“幻影行”)出现在查询范围内,这些行是事务开始后由其他事务插入并提交的。为解决幻读问题,数据库系统提供了最高的“序列化(Serializable)”隔离级别,该级别通过更严格的锁定机制,使得事务执行看起来像是按照某种预定顺序进行的,从而避免了幻读。
总结来说,随着事务隔离级别的递增(从“读未提交”到“读已提交”再到“可重复读”和“序列化”),数据库系统逐渐加强对并发事务间相互影响的控制,以防止数据不一致性问题。然而,更高的隔离级别往往意味着更低的并发性能,因此在实际应用中需要根据业务需求和性能考量,选择合适的事务隔离级别。还是一句话具体问题具体分析,不同的业务场景,需要使用不同的业务代码,切勿可生搬硬套.
以上就是博主关于索引和事务的全部总结了,具体的,更深入的一些细节,例如事务在各个隔离级别都用了哪些锁之类的,以及一些更深入的东西,博主也在慢慢学习,并尝试使用.所以又不知之处,还请见谅,感谢你的阅读