目录
1、索引
1.1 最左前缀法则
1.1.1、未遵守最左前缀法则。
1.1.2、使用范围查询,也会使右边的索引列失效
1.1.3 索引列运算会造成索引列失效
1.1.4 字符串不加引号,索引也会失效
1.1.5 模糊查询索引部分失效
1.1.6 or连接有可能造成索引失效
1.1.7 数据分布影响索引是否失效
1.2 SQL 提示
1.3 覆盖索引
1.4 前缀索引
1.5 单列索引&联合索引
1.6 索引的设计原则
2、SQL优化
2.1、insert优化
2.2 主键优化
2.3 order by 优化
2.4 gruop by优化
2.5 limit 优化
2.6 count 优化
2.7 update 优化
2.8优化总结
3、锁
3.1 全局锁
3.2 表级锁
3.2.1、表共享读锁(读锁)
3.2.3 元数据锁(MDL)
3.2.4、意向锁
3.3 行级锁
3.3.1 行锁的共享锁和排他锁
3.3.2 行锁:间隙锁+邻键锁
4、InnoDB引擎
InnoDB和MyISAM的区别是什么?
MyISAM索引的原理
4.1 逻辑存储结构
4.2 InnoDB引擎 内存结构
4.2.1 缓冲池
4.2.2 更改缓冲区
4.2.3 自适应哈希索引
4.2.4 日志缓冲区
4.3 InnoDB磁盘结构
4.4 后台线程
4.3 事务原理
4.3.1 Redolog
4.3.2 undolog
4.4 MVCC
4.4.1 MVCC基本概念
4.4.2 MVCC实现原理--undolog版本链在事务中的体现
4.4.3 readView介绍
MYSQL体系结构
基于B+树的索引图,包括键值、数据、指针、页/块
索引分为主键索引和二级索引:
主键索引:
数据表的主键列使用的就是主键索引。
一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引且不允许存在 null 值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。
二级索引:
聚集索引和二级索引的区别
二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。
唯一索引,普通索引,前缀索引等索引属于二级索引。
聚集索引和非聚集索引
聚集索引:
聚簇索引即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
优缺点:
优点 :
缺点 :
非聚集索引:
非聚簇索引介绍
非聚簇索引即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
非聚簇索引的优缺点
优点 :
更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的
缺点 :
如果索引了多列(联合索引),联合索引原理索引原理:联合索引(最左前缀原则)_联合索引最左原则原理_底小治的博客-CSDN博客
索引失效情况
要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列,如果跳过一列,索引部分失效(后面的字段索引失效)
如下,假定建立索引profession、age、status,那么根据最左前缀法则,第一条语句是按索引方式查询的,第二个也是,第三个也是,而第四个缺少了profession索引,查询时并未按索引方式查询,第五个也不符合最左前缀法则,不按索引方式进行查询。
由图可见status、age索引失效
而采用>=查询,可以避免索引失效的问题
对phone建立索引,如果对其列数据做运算操作
select * from tb_user where substring(phone,10,2)='15'
可得执行结果,执行了全表扫描,索引失效
字符串类型字段使用时,不加引号,索引将失效
explain select*from tb_user where profession='软件工程'and age=31 and status=0;
explain select*from tb_user where phone=17855973545
如果仅仅是尾部模糊匹配,索引不会失效,如果是头部模糊匹配,索引失效
尾部模糊匹配,索引不失效
select *from tb_user where profession like '软件%'
头部模糊匹配,索引失效
select* from tb_user where profession like '%工程';
用or分隔开的条件,如果or前的条件中列有索引,而后面的列没有索引,那么涉及的索引都不会被用到
由于age没有索引,所以即便id phone有索引,索引也会失效,所以需要针对age也要建立索引
如果MYSQL评估使用索引比全表更慢,则不使用索引
如果说查询出来的数据占表的大部分,走全表搜索比走索引快时,会走全表扫描,不走索引
SQL提示是优化数据库的重要手段,简单来说就是在SQL语句中加入一些人为的提示来达到优化操作的目的
创建一个索引,该索引包含查询中用到的所有字段,称为“覆盖索引”。
使用覆盖索引,MySQL 只需要通过索引就可以查找和返回查询所需要的数据,而不必在使用索引处理数据之后再进行回表操作。
覆盖索引可以一次性完成查询工作,有效减少IO,提高查询效率。
也就是说,查询的列和索引的列一致,可以使用覆盖索引,查询中应该尽量使用覆盖索引,减少使用 select * from
。
创建联合索引
ALTER TABLE `staffs` ADD INDEX idx_staffs_nameAgePos(`name`, `age`, `pos`);
使用覆盖索引查询
EXPLAIN SELECT `name`,age,pos FROM staffs WHERE `name`='July' AND age=25 AND pos='dev';
以下这些查询也可以使用覆盖索引:
-- 范围查询
EXPLAIN SELECT `name`,age,pos FROM staffs WHERE `name`='July' AND age>25 AND pos='dev';
-- 查询条件中使用部分字段
EXPLAIN SELECT `name`,age,pos FROM staffs WHERE `name`='July' AND age=25;
-- 查询列中使用部分字段
EXPLAIN SELECT `name` FROM staffs WHERE `name`='July' AND age=25;
单列索引
单列索引与联合索引
如果要一次性插入上千、万条数据,可以采用批量插入、手动提交事务、主键顺序插入等
批量插入:
大批量插入数据:
如果一次性需要插入大量数据,使用insert语句的插入性能低下,可以采用load指令进行插入
手动提交事务
主键顺序插入
主键顺序插入性能高于乱序插入
主键设计原则:
1、满足业务需求的情况下,尽量降低主键的长度
2、插入数据时,尽量按顺序插入,选择使用AUTO_INCREMENT作为自增主键
3、尽量不要使用UUID做主键或其他自然主键,如身份证号(乱序插入造成页分裂,过长的主键,检索起来也耗时耗资源)
数据组织方式:在InnoDB存储引擎中,表数据是根据主键顺序组织存放的,这种存储方式称为索引组织表
主键顺序插入时,数据按顺序存满各页,如下图1page存满8个行数据,满了之后第二页开始存储第九个行数据,第二页写满了之后写第三页。
主键乱序插入时 ,会产生页分裂
要插入键值为50的行数据时,因为B+树存储叶子节点是按顺序存储的,需要放入1page,1page还有部分空间,但是存储空间小于键值为50的行数据,无法插入1page
此时会开辟一个新数据页,将1page中一半page转入到新page页,重新设置指针,从而产生页分裂。
当删除记录时,可能会产生页合并,产生页合并的阈值可以由自己设置 MERGE_THRESHOLD
删除13、14、15、16时,会产生合并
优化方法:
1、根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
2、尽量使用覆盖索引,一个索引包含了所有需要查询的字段值,那么就称为覆盖索引
3、多字段排序,如一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)
4、如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size
数据库排序主要用以下两种方式
using index 性能更好,using filesort差一些,尽量使用using index进行排序
如
select id,age,phone from tb_user order by age
可以用explain 查询该语句具体信息
可知该语句采用的是using filesort,性能较差
将age,phone设置为索引,再进行排序
create index_idx _user_age_phone on tb_user(age,phone)
再进行排序,可用explain查看该语句信息,可知语句采用的是using index
对phone,age进行排序,并用explain查看该语句信息,可以看到此语句用了两种排序方式
因为建立索引时先对age建立,再对phone建立索引,而排序时两者顺序颠倒,违反最左前缀原则
什么是最左前缀原则:什么是最左前缀原则?什么是最左匹配原则?_社会搬运工的博客-CSDN博客
所以使用了 using filesort
对于升序,降序排序也可以做优化,只要建立对应的索引即可
通过创建索引提高gruop by语句的性能
在分组操作时,可以通过索引来提高效率
分组操作时,索引的使用也是满足最左前缀法则的,如不满足,则会采用临时表,降低效率
各类索引失效的情况以及解决方法:
首先建立索引
create index idx_user_pro_age_sta on tb_user(profession,age,status)
分析下列语句
explain select profession,count(*) from tb_user group by profession;
可得性能分析为,可见用到了索引
再分析以下语句
explain select age,count(*) from tb_user group by age
见性能分析,用到了临时表,不满足最左前缀原则,age是乱序的
再分析以下语句
explain select profession,age,count(*) from tb_user group by profession,age
见性能分析,符合最左前缀原则
再分析以下语句
explain select age,count(*) from tb_user where profession ='软件工程' group by age
见性能分析,符合最左前缀原则
优化思路:一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引+子查询的形式进行优化
设置id为主键索引,,根据排序进行分页查询,建立sql语句
select * from tb_sku s,(select id from tb_sku order by id limit 9000000,10) a where s.id=a.id
InnoDB引擎执行count时,需要将数据一行一行从引擎内读出来,然后累积计数
优化思路:自己计数
count(*)性能最优,尽量使用count(*)
在字段值没有建立索引的情况下,用where或其他的方式去绑定字段值更新,会触发表锁,让此表在此期间无法进行修改。如语句
update course set name='123' where name='PHp';
触发表锁,在commit之前,无法对此表进行修改
优化方式:对要进行where或其他方式绑定的字段建立索引,如上语句,即对name建立索引
create index_idx_course_name on course(name)
如此便不会加表锁,避免了行锁升级为表锁
优化:
MYSQL中的锁,按照锁的颗粒度可分为三类
1、全局锁:锁定数据库中的所有表
2、表级锁:每次操作锁死一张表
3、行级锁:每次操作锁住对应的行数据
全局锁应用场景:做全库的逻辑备份,对所有表进行锁定,从而获取一致性视图,保证数据的完整性。
加锁后整个数据库实例就处于只读状态,后续的DML、DDL等语句,已经更新操作的事务提交语句都将被阻塞。
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用于MyISAM,InnoDB,BDB等存储引擎中
对于表锁,分为两类
读锁阻塞更新/写操作,当其他用户想对加了读锁的表施加写操作,需要等释放读锁后才能操作
读操作并不会阻塞
3.2.2、表独占写锁(写锁)
写锁阻塞其他用户的读写操作,当前对其上锁的操作用户可对其进行读写操作
总结:
读锁不会阻塞其他客户端的读,但是会阻塞写。写锁会阻塞其他客户端的读,也会阻塞其他客户端的写
语法:
1、加锁:lock tables 表名... read/write
2、释放锁:unlock tables /客户断开连接
MDL加锁过程是系统自动控制,无需显示使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据一致性,在表上有活动事务时,不可对元数据进行写入操作
在MYSQL5.5 中引入MDL,当对某一张表进行增删改查时,加入了MDL读锁(共享),当对表结构进行变更操作时,加入MDL写锁(排他)
为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中加入意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少锁的检查。
举个例子,线程A对第三行数据进行更新操作,从而对id为3的行数据上了行锁,同时,也给整个表上了意向锁。当线程B前来操作这个表时,先会判断该意向锁与线程要上的锁是否兼容;
如果不兼容,只能等待线程A将锁释放。
意向锁与其他锁的兼容性
意向锁:
1、意向共享锁(IS):与表锁共享锁(read)兼容,与表锁排他锁(write)互斥
2、意向排他锁(IX):与表锁共享锁(read)及排他锁(write)都互斥。意向锁之间不会互斥
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。
InnoDB的数据是基于索引组织的,行锁是通过对索引的索引项加锁实现的。
对于行级锁,主要分为以下三类
1、行锁,锁定单个行记录的锁,防止其他事务对此进行update和delete。在RC、RR隔离级别下都支持。
2、间隙锁(next-key):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
3、临键锁(next-key lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙GAP。在RR隔离级别下支持
其中 隔离性 分为了四种:
READ UNCOMMITTED:可以读取未提交的数据,未提交的数据称为脏数据,所以又称脏读。此时:幻读,不可重复读和脏读均允许;
READ COMMITTED:只能读取已经提交的数据;此时:允许幻读和不可重复读,但不允许脏读,所以RC隔离级别要求解决脏读;
REPEATABLE READ:同一个事务中多次执行同一个select,读取到的数据没有发生改变;此时:允许幻读,但不允许不可重复读和脏读,所以RR隔离级别要求解决不可重复读;
SERIALIZABLE: 幻读,不可重复读和脏读都不允许,所以serializable要求解决幻读;
行锁使用的注意事项:
InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 UPDATE
、DELETE
语句时,如果 WHERE
条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加锁。这个在我们日常工作开发中经常会遇到,一定要多多注意!!!
不过,很多时候即使用了索引也有可能会走全表扫描,这是因为 MySQL 优化器的原因。
1、共享锁:允许一个事务去读一行,组织其他事务获得相同数据集的排他锁
2、排他锁:允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁
索引上的范围查询(唯一索引) 会在该范围内,全部添加临键锁
对id为5的行数据执行更新操作,会在id=3和id=7之间加间隙锁。
验证如下 查看锁的类型,确定是间隙锁
开启线程B,想在之间插入数据
进入阻塞状态,需等线程A释放间隙锁才可。
1、MyISAM不支持行级锁,在并发条件下表现差
2、MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别,具有提交(commit)和回滚(rollback)事务的能力。并且,InnoDB 默认使用的 REPEATABLE-READ(可重读)隔离级别是可以解决幻读问题发生的(基于 MVCC 和 Next-Key Lock)。
3、MyISAM不支持外键。外键对于维护数据一致性非常有帮助,但是对性能有一定的损耗。因此,通常情况下,我们是不建议在实际生产项目中使用外键的,在业务代码中进行约束即可!
4.是否支持数据库异常崩溃后的安全恢复
MyISAM 不支持,而 InnoDB 支持。
使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 redo log
5.是否支持 MVCC
MyISAM 不支持,而 InnoDB 支持。
讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提高性能
6.索引实现不一样。
MyISAM中的索引方案_myisam的索引结构_glenshappy的博客-CSDN博客
见MyISAM中的索引方案_myisam的索引结构_glenshappy的博客-CSDN博客
表空间:一个mysql实例可以对应多个表空间,用于存储记录、索引等数据
段:分为数据段,索引段、回滚段,InnoDB是索引组织表,数据段就是B+树的叶子节点,索引段即为B+树的非叶子节点。段用来管理多个区。
区:表空间的单元结构,每个区的大小为1M。默认情况下,InnoDB存储引擎页大小为16k,即一个区中有64个连续的页。
页,是InnoDB存储引擎磁盘管理的最小单元,每个页的大小默认为16kb。为了保证页的连续性,InnoDB的存储引擎每次从磁盘申请4-5个区。
整体流程概述:
当数据库进行业务操作时,会直接操作缓冲区,缓冲区倘若没有数据,会从磁盘中读入至缓冲区中。缓冲区的数据会以一定时机,频率刷新到磁盘当中,在磁盘当中永久化的保存下来。
重做日志,记录的是事务提交时数据页的物理修改,用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer) 以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都保存到日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
脏页:缓冲区的数据变更,而还未写入磁盘,称为脏页
当脏页刷新到磁盘发生错误时,可以通过Redolog进行恢复
回滚日志,用于记录数据被修改前的信息,用于事务回滚操作 ,作用包含两个:提供回滚和MVCC(多版本并发控制)
当前读;
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如
快照读:
简单的select(不加锁)就是快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
开启Repeated Read 后,后面的select访问的都是第一个select的版本
不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
当前读和快照读,以及他们的区别:
当前读和快照读 - 墨天轮
MVCC :
多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MYSQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log 日志,readView.
记录中的隐藏字段
例如事务2、3、4、5开启,当事务2进行了修改操作时,形成undolog版本链如下所示
当事务3进行了修改操作,版本链更新,成如图所示
当事务4开始后,再次更新版本链,如图所示
readView
ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护当前活跃的事务(未提交的)id
ReadView中包含了四个核心字段
不同隔离级别,生成的ReadView的时机不同
在RC级别下,在事务中每一次执行快照时生成ReadView
那么,如何去判定事务4中查询的是哪个历史版本的数据呢?
根据之前的规则,并且按照版本链从4倒回去找,直到找到符合规则的历史版本数据,可以找到
这个符合规则,那么事务4的快照读查询到的便是事务2修改后的记录。
在RR级别下,仅在事务第一次执行快照读时生成ReadView,后续重复用ReadView