目录
[4.1 主键索引](#4.1 主键索引)
[4.2 普通索引](#4.2 普通索引)
[4.3 唯一索引](#4.3 唯一索引)
[4.4 全文索引](#4.4 全文索引)
[4.5 聚集索引](#4.5 聚集索引)
[4.6 非聚集索引](#4.6 非聚集索引)
[4.7 索引覆盖](#4.7 索引覆盖)
[5.1 自动创建](#5.1 自动创建)
[5.2 手动创建](#5.2 手动创建)
[5.3 查看索引](#5.3 查看索引)
[5.4 删除索引](#5.4 删除索引)
[9.1 读未提交(最低级别)](#9.1 读未提交(最低级别))
[9.2 读已提交(常用级别)](#9.2 读已提交(常用级别))
[9.3 可重复读(MySQL默认级别)](#9.3 可重复读(MySQL默认级别))
[9.4 串行化(最高级别)](#9.4 串行化(最高级别))
一、引言
这一期来和大家聊聊什么是索引和事务,不过索引背后深层的原理我自己也没有完全明白所以不会讲太多,但是基本的底层的数据结构我还是熟悉的那么就进入这期的内容吧。
二、什么是索引
MySQL的索引是⼀种数据结构,它可以帮助数据库⾼效地查询、更新数据表中的数据。索引通过 ⼀定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。
MySQL 索引类似于书籍的⽬录,通过指向数据⾏的位置,可以快速定位和访问表中的数据,⽐如 汉语字典的⽬录(索引)⻚,我们可以按笔画、偏旁部⾸、拼⾳等排序的⽬录(索引)快速查找到需要的字。
三、索引的数据结构B+树
先介绍一下什么是B+树,B+树是一种经常用于数据库和文件系统等场合的平衡查找树它的特点有:
1.保持数据稳定有序,插入与修改有稳定的时间复杂度
2.非叶子节点具有索引作用,不存储数据只要叶子节点保留真实数据
3.所有叶子节点构成一个有序链表,可以按照key排序的次序依次遍历全部数据
叶子节点当中的数据都是连续的,且相互连接,便于区间查找和搜索,并且非叶子节点的数据都在叶子节点当中,再相同书高的情况下,查找任意元素的时间复杂度**O(logn)**都一样。
四、索引分类
其实索引分类看着多,但是理解起来都很简单,我用大白话给大家讲明白,不搞复杂的定义:
4.1 主键索引
最常用、最基础的索引,就是表中的主键(比如id)对应的索引。主键是唯一且非空的,所以主键索引能确保每一条数据都能被唯一定位,比如我们查id=10的数据,通过主键索引就能直接找到,不用遍历全表。
4.2 普通索引
最普通的索引,没有任何限制,想给哪个字段建就给哪个字段建,比如给"姓名"字段建普通索引,查姓名的时候就能加快速度,但允许字段值重复,也允许为空。
4.3 唯一索引
和普通索引很像,但有一个限制:索引对应的字段值必须唯一,不能重复,但允许为空(注意和主键索引区分,主键是唯一且非空)。比如给"手机号"字段建唯一索引,就能避免出现两个相同的手机号,同时也能加快手机号的查询速度。
4.4 全文索引
专门用来查"文本内容"的索引,比如文章标题、文章内容,普通索引查不了模糊匹配的长文本,全文索引就可以。比如想查包含"MySQL"的文章,用全文索引就能快速筛选出来,不过日常开发中用得不算特别多,一般只有做文本检索的时候会用。
4.5 聚集索引
这个稍微注意一下,它不是单独的一种索引类型,而是一种"存储方式"。聚集索引的叶子节点,存的是整个数据行的内容,而不是指针。MySQL中,主键索引默认就是聚集索引,如果没有主键,MySQL会自动找一个非空唯一字段当聚集索引,找不到就自己生成一个隐藏的聚集索引。简单说,聚集索引就是"索引和数据存在一起"。
4.6 非聚集索引
和聚集索引对应,非聚集索引的叶子节点,存的不是完整数据行,而是数据行的指针(指向聚集索引的位置)。比如给"年龄"建非聚集索引,查年龄的时候,先通过非聚集索引找到指针,再通过指针去聚集索引里找完整数据,比聚集索引多一步,但也比没索引快很多。
4.7 索引覆盖
这个其实是一种"查询优化的情况",不算严格意义上的索引类型。简单说,就是我们查询的字段,刚好都在索引里,不用再去查实际的数据行。比如给"姓名"和"年龄"建了联合索引,我们查"姓名=张三 and 年龄=20",这两个字段都在索引里,MySQL直接查索引就能拿到结果,不用再去访问数据表,速度特别快,这就是索引覆盖。
五、使用索引
使用索引其实很简单,主要涉及自动创建、手动创建、查看索引、删除索引这4个场景,还是用大白话讲,不写太复杂的语法,日常开发够用就行,新手也能轻松看懂。
5.1 自动创建
这个最省心,不用我们手动操作,MySQL会自动帮我们创建索引,最常见的就是主键索引。当我们给表设置主键(比如id字段)时,MySQL会自动为这个主键字段创建主键索引,目的就是保证主键唯一、非空,同时加快主键的查询速度。
比如我们建表时写了id INT PRIMARY KEY,这时候MySQL就会自动创建一个主键索引,不用我们额外写语句,相当于"自带福利",省了我们不少事。
5.2 手动创建
除了自动创建的主键索引,其他类型的索引(普通索引、唯一索引、全文索引等),都需要我们手动创建,常用的有两种方式,适合不同场景。
第一种:建表时手动指定索引,和字段定义一起写。比如建user表时,给phone字段建唯一索引,给name字段建普通索引,一次性搞定:
CREATE TABLE user (id INT PRIMARY KEY, phone VARCHAR(11) UNIQUE, name VARCHAR(20), INDEX idx_user_name(name));
第二种:表已经建好,后续需要新增索引。比如表建好后,发现需要给content字段建全文索引,给age字段建普通索引,就用CREATE INDEX语句:
-- 手动创建普通索引(给age字段)
CREATE INDEX idx_user_age ON user(age);
-- 手动创建唯一索引(给email字段)
CREATE UNIQUE INDEX idx_user_email ON user(email);
-- 手动创建全文索引(给content字段)
CREATE FULLTEXT INDEX idx_user_content ON user(content);
这里提醒一句,手动创建索引时,建议给索引起个有意义的名字(比如idx_表名_字段名),后续查看、删除时更方便区分。
5.3 查看索引
有时候我们忘了一张表建了哪些索引,或者想确认一下索引是否创建成功,这时候就需要查看索引,最常用、最简洁的语句就是:
SHOW INDEX FROM 表名;
比如查看user表的所有索引,就写SHOW INDEX FROM user;,执行后会显示索引的名称、对应的字段、索引类型等信息,一目了然。
不用记太复杂的语句,这一句就够日常使用了,新手直接照搬就行。
5.4 删除索引
如果建错了索引,或者某个索引长期不用,留着还会影响数据的插入、修改速度,这时候就需要删除索引,常用两种方式,对应手动创建的两种场景。
第一种:删除普通索引、唯一索引、全文索引,用DROP INDEX语句,格式很简单:
DROP INDEX 索引名 ON 表名;
比如删除刚才给name字段建的普通索引,就写DROP INDEX idx_user_name ON user;。
第二种:删除主键索引,主键索引比较特殊,不能用上面的语句,需要用ALTER TABLE语句:
ALTER TABLE 表名 DROP PRIMARY KEY;
比如删除user表的主键索引,就写ALTER TABLE user DROP PRIMARY KEY;。
注意:删除索引前一定要确认好,别删错了,删了之后就没法恢复,只能重新创建了。另外,主键索引一般不建议删,删了之后主键就失去了唯一定位的作用,查询会变慢。
六、什么是事务
聊完索引,再来说事务,事务其实就是"一组不可分割的数据库操作",要么这组操作全部执行成功,要么全部执行失败,不会出现"一部分成功、一部分失败"的情况。
举个最常见的例子:转账。比如我给你转100块钱,这个操作分两步:第一步,我的账户扣100块;第二步,你的账户加100块。这两步必须同时成功,或者同时失败------如果我扣了钱,你没加到,那钱就凭空消失了;如果我没扣钱,你加了100,那钱就凭空多出来了。而事务,就是保证这两步要么都成,要么都不成,避免出现这种混乱的情况。
简单说,事务就是"要么全做,要么全不做",用来保证数据的一致性,解决多步操作的容错问题。
七、事务的核心特性
事务有四个核心特性,业内叫ACID,我不用专业术语堆砌,用大白话逐个讲明白,保证大家能看懂、能记住:
-
原子性(A):就是刚才说的,事务是一个不可分割的整体,里面的操作要么全成,要么全不做,没有中间状态。比如转账,扣钱和加钱,不会只成一个。
-
一致性(C):事务执行前后,数据的完整性要保持一致。比如转账前,我和你的账户总金额是2000块,转账后,总金额还是2000块,不会多也不会少,这就是一致性。
-
隔离性(I):多个事务同时执行的时候,它们之间不会互相干扰,各自的操作都是独立的。比如我给你转账的同时,你给别人转账,这两个事务互不影响,不会出现我扣了钱,因为你的事务影响,导致我这边数据出错的情况。
-
持久性(D):一旦事务执行成功,它对数据的修改就会永久保存,哪怕数据库崩溃、重启,修改后的结果也不会丢失。比如转账成功后,我扣了100,你加了100,就算数据库重启,这个结果也还在。
这四个特性里,隔离性稍微复杂一点,后面专门单独讲隔离级别,这里先记住这四个特性的核心意思就好。
八、使用事务
MySQL中使用事务也很简单,常用的语法就3个:开启事务、提交事务、回滚事务,还是结合转账的例子来讲,大家一看就懂。
首先,开启事务:START TRANSACTION; (也可以简写为BEGIN;)
然后,执行事务里的操作,比如我的账户扣100,你的账户加100:
UPDATE user SET money = money - 100 WHERE id=1; -- 我的账户
UPDATE user SET money = money + 100 WHERE id=2; -- 你的账户
如果这两步都执行成功,就提交事务:COMMIT; 提交后,修改就永久生效了。
如果执行过程中出错了(比如第二步报错),就回滚事务:ROLLBACK; 回滚后,所有操作都会撤销,回到事务开启前的状态,比如我的账户不会扣钱,你的账户也不会加钱,避免数据出错。
另外补充一句,MySQL默认是"自动提交事务"的,也就是每执行一条SQL语句,就自动提交一次,所以如果想手动控制事务,一定要先执行START TRANSACTION; 关闭自动提交。
九、事务的隔离性和隔离级别
前面讲事务特性的时候,提到了隔离性------多个事务同时执行,互不干扰。但实际上,完全的"互不干扰"会影响性能,所以MySQL设计了不同的隔离级别,用来平衡"隔离性"和"性能",一共有4个隔离级别,从低到高排列,我逐个讲,还是大白话,不搞复杂。
首先要知道一个前提:如果不设置隔离级别,多个事务同时执行,可能会出现3个问题,先了解这3个问题,再看隔离级别怎么解决它们:
-
脏读:一个事务读取到了另一个事务"未提交"的修改。比如我给你转100,执行了扣钱操作,但没提交事务,这时候你查询账户,看到多了100,结果我这边回滚了事务,你看到的那100就是"脏数据",这就是脏读。
-
不可重复读:同一个事务中,多次读取同一数据,结果不一样。比如你在一个事务里,第一次查我的账户有1000块,这时候我另一个事务给我转了500并提交,你再查我的账户,变成了1500,同一个事务里两次查询结果不同,这就是不可重复读。
-
幻读:同一个事务中,多次查询同一范围的数据,结果的行数不一样。比如你查询账户金额大于500的用户,第一次查到3个人,这时候我新增了一个金额600的用户并提交,你再查,变成了4个人,就像出现了"幻觉",这就是幻读。
接下来讲4个隔离级别,每个级别能解决什么问题,以及MySQL的默认级别:
9.1 读未提交(最低级别)
允许一个事务读取另一个事务未提交的修改,隔离性最差,会出现脏读、不可重复读、幻读所有问题,性能最好,但实际开发中基本不用,因为数据太不安全了。
9.2 读已提交(常用级别)
一个事务只能读取另一个事务"已提交"的修改,能解决脏读问题,但还是会出现不可重复读和幻读。这个级别在很多业务场景中都能用,比如电商的订单查询,性能和隔离性比较均衡,也是很多数据库的默认级别(但MySQL不是)。
9.3 可重复读(MySQL默认级别)
这个是MySQL默认的隔离级别,保证同一个事务中,多次读取同一数据,结果始终一致,能解决脏读、不可重复读问题,但还是会出现幻读(不过MySQL通过自身的机制,已经基本避免了幻读的影响,日常开发中可以忽略)。
简单说,就是一个事务开启后,不管其他事务怎么修改数据、提交,这个事务里读到的数据始终是开启时的样子,不会变,这就是可重复读。
9.4 串行化(最高级别)
隔离性最高,完全禁止多个事务同时执行,相当于所有事务排队执行,不会出现脏读、不可重复读、幻读任何问题,但性能最差,因为排队执行太慢了,只有对数据一致性要求极高的场景才会用(比如银行转账的核心业务),日常开发基本不用。
最后总结一下:隔离级别越高,数据越安全,但性能越差;级别越低,性能越好,但数据越不安全。日常开发中,用MySQL默认的可重复读就够了,特殊场景再根据需求调整。
十、总结
好了,这一期关于MySQL索引和事务的基础内容,就全部讲完啦。其实我一开始就说过,不深讲底层复杂原理,只给大家讲日常开发能用得上、能看懂的基础,毕竟我自己也还在慢慢摸索更深层次的知识,一起学习、一起进步~
简单回顾一下这一期的核心重点,方便大家快速梳理和记忆,不用再回头翻前面的内容:
关于索引:它就是数据库的"目录",核心作用是加快查询速度,底层用的是B+树这种数据结构,特点是稳定、高效,适合磁盘存储。索引分很多种,主键索引是MySQL自动创建的,其他索引需要手动创建,同时要会查看、删除索引,还要记住,不是建了索引就有用,避免踩索引失效的坑(后续有机会再细聊)。
关于事务:它就是一组"要么全成、要么全不做"的数据库操作,核心是保证数据一致性,最典型的例子就是转账。事务有ACID四个特性,日常用的时候,记住开启、提交、回滚三个核心语法就行;隔离级别不用死记硬背,知道MySQL默认是可重复读,日常开发够用,特殊场景再调整就好。
其实索引和事务,是MySQL里最基础也最常用的两个知识点,不管是日常开发,还是面试基础提问,都大概率会用到。这一期就先讲到这里,后续如果我摸索明白了更深层的原理,再跟大家分享