MySQL 索引,存储引擎,事务

目录

前言基础知识MySQL增删改查...代码讲解

MySQL服务器端的逻辑架构说明

1. 存储引擎

1.1. INNODB 存储文件结构

1.2. MYISAM引擎

1.3. Archive引擎

1.4. CSV引擎

1.5. Memory引擎

2. 索引

2.1 常见索引

2.2. 索引的声明和使用

2.3. 那些字段适合创建索引?

2.4. 索引的优化

2.5. 那些情况不适合索引

3. 性能优化工具

3.1. EXPLAIN性能优化工具的使用

4. 索引优化案列和失效案列

4.1. 索引失效案列

4.2. 索引优化

5. 数据库设计范式

5.1. 范式(Normal Form)

5.2 范式的优缺点

6. 事务

6.1. 事务的特性ACID

6.2. 事务的状态

6.3. 事务语法

6.4. 事务的并发问题

6.5. 隔离级别

6.6. MySQL事务日志

6.7. 锁机制


前言基础知识MySQL增删改查...代码讲解

MySQL服务器端的逻辑架构说明

  • 客户端:(JDBC,.NET,PHP,ODBC...)
  • 连接层:连接池一共多个客户端的连接线程 CTP连接池 和线程连接池
  • SQL层,服务层:SQL接口(接受sql和返回查询的结果集)-->解析器(语法,语义解析)-->优化器(优化SQL语句)-->缓存(以key-value的方式缓存)
  • 引擎层:和文件系统做交互,处理数据,INNODB MYISAM ...
  • 文件系统:存储数据

MySQL 索引,存储引擎,事务_第1张图片

MySQL 索引,存储引擎,事务_第2张图片

1. 存储引擎

默认的存储引擎是INNODB 存储引擎是正对表的“表处理器”
INNODB引擎:支持外键,事务存储引擎
  • 支持事务,因此可以提交会回滚数据,保证数据完整性
  • 为处理巨大数据量大的引擎
  • 锁粒度:支持行锁。

1.1. INNODB 存储文件结构

# .frm 文件是存放的表结构,表的定义信息;
# .ibd 表数据和索引;(数据既索引)
# INNODB写的处理效率差一些,对内存要求高。

1.2. MYISAM引擎

  • 锁粒度:表锁
  • 节省资源,消耗少,业务简单
  • 不支持事务
# .frm文件是存放的表结构,表的定义信息;
# .myd文件是存放着表中的数据;
# .myi文件存放着表的索引信息;

1.3. Archive引擎

  • 主要用于数据存档,存储,仅仅支持支持查询和插入
  • 有很好的压缩机制 .arz
  • 锁粒度:采用行级锁

1.4. CSV引擎

  • .csm 存储源文件
  • .csv 存储数据的

1.5. Memory引擎

  • 至于内存中的表
  • 大量小数据,用了就不用的就很合适

2. 索引

索引是一种数据结构用于快速找到某一条数据,比如字典里面的目录页,图书馆的存放目录。
# 优点:提高检索速率,保证数据库表中的唯一性,加速表之间的连接,减少查询中分组排序的花间
# 缺点:创建和维护索引需要耗费时间表,索引需要存储空间的,占用磁盘空间,索引减少表数据的更新速度

索引的数据结构可以参考这篇文献

MySQL索引的数据结构_杉菜酱_的博客-CSDN博客_mysql索引数据结构​shancaijiangzi.blog.csdn.net/article/details/122640854正在上传…重新上传取消

2.1 常见索引

  • 聚簇索引(主键索引):叶子节点存储的是我们的数据记录,也就是所谓的索引即数据,数据即索引。
  • 非聚簇索引 :叶子节点存储的是数据位置,不会影响数据表的物理存储顺序,也把非聚集索引称为二级索引或者辅助索引
  • 联合索引:可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引。

2.2. 索引的声明和使用

# 普通索引
CREATE TABLE index_test(
id INT,
name VARCHAR(18),
age INT
...
INDEX index_name(id) #声明索引
);

# 为已创建成功的表创建索引
ALTER TABLE index_test ADD INDEX index_name(id);
OR
CREATE INDEX index_name(id) ON index_test;

# 删除索引的方式
ALTER TABLE index_test DROP INDEX index_name;
OR
DROP INDEX index_name ON index_test;

# 唯一索引
CREATE TABLE index_test(
id INT,
name VARCHAR(18),
age INT
...
UNIQUE INDEX index_name(id) #声明索引
);

# 为已创建成功的表创建唯一索引
ALTER TABLE index_test ADD UNIQUE INDEX index_name(id); 

# 主键索引
CREATE TABLE index_test(
id INT PRIMARY KEY ,
name VARCHAR(18),
age INT
...
#声明索引
#主键索引是隐式创建
);

# 全文索引
CREATE TABLE index_test(
id INT,
name VARCHAR(18),
age INT,
detail TEXT,
...
FULLTEXT INDEX index_detail(detail) # 声明索引
);

# 为已创建成功的表创建全文索引
ALTER TABLE index_test ADD FULLTEXT INDEX index_detail(detail);

# 单列索引
CREATE TABLE index_test(
id INT,
name VARCHAR(18),
age INT
...
UNIQUE INDEX index_name(id) # 声明索引
);
#为已创建成功的表创建单列索引
ALTER TABLE index_test ADD UNIQUE INDEX index_name(id);

# 多列索引
CREATE TABLE index_test(
id INT,
name VARCHAR(18),
age INT
...
INDEX index_name_id(id,name) # 声明索引
);

#为已创建成功的表创建多列索引
ALTER TABLE index_test ADD FULLTEXT INDEX INDEX index_name_id(id,name);

# 空间索引
......

#查看索引的方式
SHOW INDEX FROM employees;
#8.0后 索引支持了降序索引 和 隐藏索引

2.3. 那些字段适合创建索引?

  • 对那些唯一性的列创建索引,唯一索引或者主键索引
  • 经常在WHERE作为条件的字段
  • 经常用作A -ORDER BY 和B- GROUP BY的字段
# 如果AB都出现可以考虑一下建立联合索引并且通常以A字段在前B写在后面
  • UPDATE,DELETE的 WHERE条件列也可以创建索引
  • DISTINCT(去重)字段需要创建索引
  • 多表连接时,创建索引的注意事项
# 连接表不能超过3张
# 对WHERE条件创建索引
# 对用于连接表的字段创建索引,连接字段多表中类型必须一致
  • 在创建索引时候选择数据类型小的去创建索引
  • 使用字符串前缀创建索引-前缀索引
# note:在varchar字段创建索引最好设置字段索引长度阿里巴巴手册规定长度为20
  • 区分度高(散列性高)的适合作为索引
# note:超过33%散列度即适合作为索引

2.4. 索引的优化

  • 使用最频繁的列放在联合索引的最左测
  • 在多个字段都要创建索引的情况下,联合索引优于单值索引
  • 限制索引的数目:不超过6个
# note:每个索引都会占用存储空间,索引的创建会降低插入,修改和删除的性能,会增加MYSQL优化器生成执行计划的时间,降低了查询的性能。

2.5. 那些情况不适合索引

  • 在WHERE,GROUP BY,ORDER BY中使用不到的字段不需要索引
  • 在数据量小的情况下没必要创建索引
  • 有大量重复数据的列上不要创建索引
  • 避免对经常更新的表创建过多的索引,特别是在更新频繁的字段
  • 不要以无序的值作为索引
  • 删除不再使用和很少使用的索引
  • 不要定义冗余或者重复的索引
# note:也就是在一个列定义多种索引

3. 性能优化工具

3.1. EXPLAIN性能优化工具的使用

#基本语法
EXPLAIN SELECT * FROM empl1;

EXPLAIN SELECT * FROM empl1;执行结果

3.3.1. EXPLAIN优化字段说明

# id :
id如果相同,可以认为是一组,从上向下顺序执行
所在组中id值越大,优先级越高,越先执行
关注点:每个id号表示一趟独立查询,一个sql语句查询的趟数越少越好

# select_type :SELEC关键字对应查询的类型,确定小查询在大查询中扮演的是一个什么角色
SIMPLE:查询语句中不包含“UNION”或者子查询都算是“SIMPLE”类型
PRIMARY:包含“UNION”,“UNION ALL”或者子查询的来说,它是有几个小查询组成,其中最左边的查询的select_type的值就是“PRIMARY”,其余的
全部是“UNION”
SUBQUERY:如果包含子查询语句不能够转换对应的连表查询形式,并且该子查询不是相关子查询,该子查询的第一个
‘SELECT’关键字代表的那个查询的‘select_type’就是‘SUBQUERY’,如果该子查询是相关子查询则‘SELECT’关键字代
表的那个查询的‘select_type’就是‘DEPENDMENT SUBQUERY’
 
# table :表名
#查询的每一行记录对应一个单表
EXPLAIN SELECT * FROM empl1;
#驱动表和被驱动表(语句左侧的是驱动表右侧的是被驱动表)

# partitions :

# type :针对单表的访问方法
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique > unique_subquery >
index_subquery > rang > index > all
其中前4个select的访问方式是比较重要的。SQL性能优化的目标:至少要达到rang级别,要求是ref级别,最好是const级别

# possible_key :
执行计划中可能使用到的索引

# key :
实际执行中正真使用的索引

# key_len :
实际使用的索引长度(即:字节数)帮你检查是否充分的利用上了索引

# ref :
当使用索引等值查询时与索引列进行等值匹配的对象信息

# rows :
预计需要读取的记录数

# filtered :
某个表经过搜索条件过滤后剩余记录条数的百分比

# Extra :
一些额外的信息

4. 索引优化案列和失效案列

note:(索引命名即为索引创建的字段顺序)例如 index_id_name_age
即为 CREATE INDEX index_id_name_age(id,name,age) ON table_name;

4.1. 索引失效案列

  • WHERE后面条件字段为多个时,联合索引效果最好
index_id,index_id_name,index_id_name_age三个索引效果最好的时index_id_name_age

...WHERE id = ? AND name = ? AND age = ? # index_id_name_age效果最好 > index_id_name > index_id
  • 最佳就左法则
MySQL可以为多个字段创建索引,一个索引可以包括16个字段。对于多列索引,过滤条件
要使用索引必须按照索引建立时的字段顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用
例:学生表中有 id,name,age,addir等字段 我们建立了联合索引(index_id_name_age),
当查询语句WHERE后面使用字段都会影响索引的使用
...id = ? AND name = ? AND age = ? # 完全使用index_id_name_age索引
...id = ? AND age = ? # 不完全使用index_id_name_age索引,只能使用id这个字段索引,虽然age字段也索引了,但是跳过了nage字段因此不能使用
...name = ? AND age = ? # 索引失效,必须最先使用id,再 name 最后age
...id = ? AND name = ? # 完全使用index_id_name_age索引
  • 主键顺序递增
  • 计算,函数,类型转换(自动或者手动)导致索引失效
# 函数导致的索引失效
...WHERE LIKE name 'abc%'; # 使用索引
...WHERE `LEFT`(name,3) = 'abc'; # 索引失效
  • 类型转换导致的索引失效
# 字段name为VARCHAR类型,并设置了索引,以下情况索引失效
...WHERE name = 123; # 索引失效(类型转换了INT转化为VARCHAR)
...WHERE name = '123'; # 使用索引
  • 范围条件右侧的列索引失效问题以及优化
# 创建索引index_id_age_name
...WHERE id = 10 AND age > 20 AND name = 'abc'; # 索引不完全被使用只会使用索引中id字段和name字段
age后条件为范围导致age字段索引失效

# 优化
将索引字段创建顺序改变,将判断条件是范围的字段创建时排在最右测 index_id_name_age
总结:保持良好的开发习惯,我们在编写索引时应该将范围的字段习惯性的放在最右
  • 不等于!= 或者 <> 也会导致索引失效
...WHERE name != 'abc'; >> 索引失效
...WHERE name <> 'abc'; >> 索引失效
  • IS NULL 可以使用索引 IS NOT NULL无法使用索引
... WHERE age IS NULL; # 正常索引
... WHERE age IS NOT NULL; # 索引失效

结论:在设计数据表时将字段设置为非空约束,可以设置默认值
  • LIKE 以通配符%开头的索引失效
...WHERE name LIKE '%abc'; # 索引失效

结论:页面搜索严禁在左模糊或者全模糊,如果需要请走搜索引擎来解决
  • OR 前后存在非索引的列,索引失效
# 现有索引index_id
...WHERE id = 10 OR name = 'abc'; # index_id失效,原因(即使使用了id索引,但是OR中其他的字段一样的要全盘检索)

4.2. 索引优化

  • 为每个 OR 字段都创建索引,或者为OR中的全部字段创建联合索引
即:新增index_name 或者 创建index_id_name
  • 数据库和表的字符集统一使用一样的字符集
原因:不同的字符集会字符集转换

优化总结:

  • 当在WHERE后面有多个字段比较时,在不能为每个字段创建索引的前提下(索引越多维护就越麻烦)应选择过滤效果最好的字段创建索引
  • 在联合索引中,过滤最好的字段卸载最左测
  • 在联合索引中,将范围判断的字段放在最右测
# ①使用最频繁的列放在联合索引的最左测
# ②在多个字段都要创建索引的情况下,联合索引优于单值索引
# ③限制索引的数目:不超过6个
note:每个索引都会占用存储空间,索引的创建会降低插入,修改和删除的性能,会增加MYSQL优化器生成执行计划的时间,降低了查询的性能。
  • 关联查询的优化
-- 左外连接

-- 内连接
对于内连接来说,在两个连接条件都有索引时,结果集小的表就为驱动表,结果集大的表就是被驱动表
  • 覆盖索引
概念:一个索引包含了满足查询结果的数据列就叫覆盖索引
  • 索引条件下推(index condition pushdown )
理解:在联合索引使用不完全的情况下,利用没使用的索引字段过滤后再回表。
# 现有索引 index_id_name_age
EXPLAIN SELECT * FROM table_name
WHERE 
id = 10 # id 字段可以索引 
AND name = '%as' # name字段由于通配符%在前 索引字段name失效
AND age > 20; # age字段由于属于范围条件 age字段索引也失效

# 假设满足 id = 10 的记录有1000条,
# 满足id = 10 AND name = '%as' 的100条 
# 满足id = 10 AND name = '%as' AND age > 20 的10条

#再没有开启ICP的情况下的执行逻辑:通过id索引找到id = 10的数据,满足条件的1000条数据,
 然后开始1000次表操作去一次找到符合name,age符合条件的记录

# 再开启ICP的情况下的执行逻辑:通过id索引找到id = 10的数据,满足条件的1000条数据,然后索引条件下推
  虽然name字段索引没有用上,但是我们索引中包含了name和age字段,于是我们就可以再找到1000满足id的记录后
  先不回表,先过滤name,age最后再回表,即使用ICP后回表次数为10次,从而提升了效率。

索引条件下推ICP的使用条件

  1. 如果访问类型为rang,ref,eq_re和ref_or_null可以使用ICP
  2. ICP可以用InnoDB 和 MyISAM,包括分表InnoDB 和 MyISAM表
  3. 对于InnoDB表,ICP仅用于二级索引。ICP的目的时减少回表的次数,从而减少I/O
  4. 当SQL使用覆盖索引时,不支持ICP
  5. 相关子查询的条件不能使用ICP
  • 其他优化策略
# EXISTS 和 IN的 选择?
# 驱动表A和被驱动表B
SELECT * FROM A WHERE c IN(SELECT c FROM B); # 当驱动表A数据多,被驱动表B数据少时适合用IN
SELECT * FROM A WHERE EXISTS (SELECT c FROM B WHERE B.c = A.c); # 当驱动表A数据少,被驱动表B数据多时适合用EXISTS

# 问: COUNT(*),COUNT(1),COUNT(字段)三者的速率?
# 答:COUNT(*),COUNT(1)执行效率差不多,但在MyISAM中由于表中默认设置了隐藏字段row_count,所以时间复杂度时O(1);
在InnoDB中时全表扫描,时间复杂度时O(n)

# 问:SELECT(*)的使用?
# 答:避免使用SELECT(*)①因为SQL在解析过程会通过数字字典将“*”转化为对应的字段名,会大大消耗资源和时间。
②无法使用覆盖索引

# 问: LIMIT 1的影响;
# 答:在没有索引的条件下LIMIT 1会再找到结果后终止继续扫面,但是再使用唯一索引的情况下再查找数据时不会全盘扫描,所以不需要LIMIT 1

# 多使用COMMIT

5. 数据库设计范式

数据库设计应该符合安全性,正确性,数据冗余,方便程度的角度考虑

5.1. 范式(Normal Form)

# 六种常见的范式,按照范式级别,从低到高
# 第一范式 -  第二范式 -第三范式 -巴斯范式 -第四范式 -第五范式
  • 第一范式:表中记录的数据具有原子性,也就是表中的字段的值为不可再拆分的最小单元(原子性)
EG:字段名称user_info  字段说明:用户信息(包含真实姓名,电话,住址)
改写符合第一范式格式:
user_info改写成三个字段,分别是真实姓名,电话,住址
  • 第二范式:在满足第一范式的前提,还必须满足数据表的每一条数据记录,都是唯一可标识的。而且所有非主键字段,都必须完全依赖主键,不能只是部分依赖主键的一部分(完全依赖)
# 通俗的理解就是一张表表示一个对象
原表:PK(球员编号,球赛编号) -> (球员名字,比赛时间,比赛得分)
# 满足第二范式写法
表1:PK(球员编号) -> (球员名字)
表2:(球员编号,球赛编号)
表3:PK(球赛编号) -> (比赛时间,比赛得分)
  • 第三范式:再满足前两个范式的前提,确保每列都和主键直接相关,而不是间接相关(直接相关)
# 原表:PK(员工ID,部门ID) -> (员工名字,员工薪资,部门名字,部门总人数)
# 满足第三范式写法
表1:PK(员工ID) -> (员工名字,员工薪资,部门名字)
表2:PK(部门ID) -> (部门名字,部门总人数)
  • 巴斯范式
  • 第四范式
  • 第五范式

5.2 范式的优缺点

优点:减小数据冗余
缺点:降低查询效率,关联了多张表,也可能使索引无效

6. 事务

概念:一组逻辑操作单元,使数据从一种状态变换到另一种状态

6.1. 事务的特性ACID

  • 原子性(atomicity):事务的操作都是不可再分的最小执行单元
  • 一致性(consisten):数据从一个合法的状态变换到另一个合法的状态
  • 隔离性(islotion):一个事务的执行不被其他事务的干扰.
  • 持久性(durability):一个事务一旦被提交,它对数据库中的数据的改变就是永久性的

6.2. 事务的状态

  • 活动的 :正在执行时状态
  • 部分提交:事务执行完再还没提交前的状态
  • 失败的:事务再部分提交或者活动状态中被某些原因打断执行
  • 中止的:事务执行了一部分而变成了失败的状态后回滚事务到执行前的状态
  • 提交的:当一个处在部分提交状态的事务将修改的数据都同步到磁盘上之后,我们就可以说该事务处在提交状态

6.3. 事务语法

# step1 :开启事务 START TRANSACTION 或者 BEGIN
# step2 :一系列的操作
# step3 :结束状态:提交(COMMIT)或者中止(ROLLBACK) 

6.4. 事务的并发问题

  • 脏写
  • 脏读
  • 不可重复读
  • 幻读

6.5. 隔离级别

隔离级别和并发性能成反比,隔离级别越高并能性就越差
隔离级别 脏写 脏读 不可重复读 幻读 加锁读
READ UNCONMITED NO YES YES YES NO
READ COMMITED NO NO YES YES NO
REPEATABLE READ NO NO NO YES NO
SERIALIZABLE NO NO NO NO YES

6.6. MySQL事务日志

事务的原子性,一致性和持久性是用redo log和undo log来保证的。
  • redo log 重做日志:提供再写入的操作,恢复提交事务修改的页操作,保证事务的持久性
原理:在事务每次提交后,把事务修改的东西全都记录一下,以免在刷盘时候宕机,可以使用记录再重新执行刷盘,从而保证持久性。WAL:先写日志再写磁盘。只有日志写入成功才算事务提交成功。

特点:

  1. redo日志是顺序写入磁盘的
  2. 执行事务过程中,redo log不断记录
  3. redo日志降低了刷盘的频率
  4. redo日志占用的空间非常小

日志的刷盘流程

innodb_flush_log_at_tx_commit 日志刷盘策略
innodb_flush_log_at_tx_commit = 1 # 默认的日志刷盘策略,安全性最好,执行最慢。
执行策略:事务执行中 > 不断的写日志redo log并写入到buffer > 事务提交立刻将buffer刷到page ache 并且立刻将日志从page ache 刷盘 (file)

innodb_flush_log_at_tx_commit = 2 : # 快,但相对不安全,在SQL宕机不影响,但操作系统宕机 前page ache 还没刷盘 就GG 
执行策略:事务执行中 > 不断的写日志redo log并写入到buffer > 事务提交立刻将日志刷到page ache 等待系统进程(每秒执行一次)将日志从page ache 刷盘 (file)

innodb_flush_log_at_tx_commit = 0 : 快,但是最不安全,SQL在事务提交后并且还没有将日志刷盘就宕机就GG
执行策略:事务执行中 > 不断的写日志redo log并写入到buffer > 事务提交 > 等待系统进程(每秒执行一次)将buffer刷到page ache 再将buffer从page ache 刷盘 (file)
  • undo log 回滚日志:回滚到行记录的某个特定的版本,保证事务的原子性,一致性。在事务中更新数据的前置操作,记录更新前的数据并写入日志
针对 INSERT # undo会记录主键值,当需要回滚时InnoDB会执行一个DELETE
针对 DELETE # undo会记录删除前记录的呢内容,当需要回滚时InnoDB会执行一个INSERT
针对 UPDATE # undo会记录修改前的被修改的字段内容,当需要回滚时InnoDB会执行一个与之相反的UPDATE将数据修改回去

6.7. 锁机制

事务的隔离性用锁机制来保证的

MySQL并发事务访问相同记录的情况

  • 写-写(脏写)
  • 读-写 或者 写-读(脏读,不可重复读,幻读)
# 采用MVCC方式的话,读-写 操作彼此并不冲突,性能更高
# 采用加锁的方式的话,读-写 操作彼此需要排队,影响性能

锁的划分

  • 从数据的操作划分类型:读锁/共享锁,写锁/排他锁,InnoDB中可以加在表上也可以在行上
# 加锁:
SELECT ... LOCK IN SHARE MODE; # S锁/读锁/共享锁
SELECT ... FOR UPDATE; # X锁/写锁/排他锁
  • 从数据操作的力度划分:表级锁,页级锁,行锁
表锁(粒度最大):开销小,并发差,无死锁问题
LOCK TABELE name READ/WRITE;
UNLOCK table_name;
  • 意向锁:解决表锁和行锁的同时存在
  • 自增锁:AUTO_INC锁是对当前含有自增字段的数据在插入时需要获取的一种特殊表级锁
  • 元数据锁:当在对表操作更删改查时对表进行元数据锁,谨防其他事务对表的结构修改。

innoDB中的行锁

  • 记录锁
  • 间隙锁 LOCK_GAP :防止幻读
  • 临键锁
  • 插入意向锁

6.7. MVCC(多版本并发控制)

MVCC(多版本并发控制) 多版本依靠undo log ,控制依靠tx_id 和 ReadView
  • MVCC只是作用在 READ COMMITTD,REPETABLE READ这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程,这样子使读-写,写-读错做并发执行,从而提高系统性能。
  • MVCC的核心在于ReadView。
  • READ COMMITTD隔离级别中 每一次的SELECT操作前都会生成一个ReadView
  • REPETABLE READ隔离级别中 只有第一次SELECT操作前生成一个ReadView,之后的查询操作都一直使用这个ReadView直到查询事务提交。

你可能感兴趣的:(MYSQL,数据库,java,开发语言,mysql)