文章目录

  • 1.事务
    • 1.概念:事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功
    • 2.事务的基本特性
      • 1.原子性:事务是不可分割的,要么完全成功,要么完全失败 mysql中通过 undo log 来实现的、
      • 2.持久性: 事务一旦提交,则其所有的修改将会保存到数据库当中。即使此时系统崩溃,修改的数据也不会丢失。redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。
      • 3.隔离性:
      • 4.一致性的实现
  • 2.sql的顺序和执行顺序:
    • 1.sql语句编写时的顺序
    • 2.sql语句执行的顺序
  • 3.索引
    • 1.索引类型:
    • 2.聚簇索引和非聚簇索引(二级索引)
    • 3.回表:
    • 4.覆盖索引:
    • 5. Explain 查看sql的执行详情
      • 1.select_type
      • 2.table
      • 3.type
      • 4.possible_keys
      • 5.Key
      • 6.key_len
      • 7.ref
      • 8.rows
      • 9.Extra
    • 6.覆盖索引可以做的sql优化:
  • 5.mysql索引创建原则:
  • 6.mysql索引失效场景:
  • 7.sql在mysql中是如何执行的
    • 1.MySQL 基本架构概览
    • 2.分析
    • 3.总结:
  • 8.SQL优化:
  • 9.Innodb为什么使用B+

1.事务

1.概念:事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功

2.事务的基本特性

1.原子性:事务是不可分割的,要么完全成功,要么完全失败 mysql中通过 undo log 来实现的、

undo log就是在修改数据前,会记录对应的操作和数据,在回滚时,会根据undo log来将被修改的内容还原

2.持久性: 事务一旦提交,则其所有的修改将会保存到数据库当中。即使此时系统崩溃,修改的数据也不会丢失。redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。

3.隔离性:

mysql锁技术以及MVCC基础

  1. mysql锁技术
    当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。

读写锁

解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:

共享锁(shared lock),又叫做"读锁"

读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。

排他锁(exclusive lock),又叫做"写锁"

写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。

总结:

通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行
事务的隔离性就是根据读写锁来实现的!!!这个后面再说。

  1. MVCC基础
    MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。

InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,
一个保存了行的创建时间,一个保存了行的过期时间,
当然存储的并不是实际的时间值,而是系统版本号。
以上片段摘自《高性能Mysql》这本书对MVCC的定义。他的主要实现思想是通过数据多版本来做到读写分离。从而实现不加锁读进而做到读写并行。

MVCC在mysql中的实现依赖的是undo log与read view

undo log :undo log 中记录某行数据的多个版本的数据。
read view :用来判断当前版本数据的可见性

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。

Mysql 隔离级别有以下四种(级别由低到高):

READ UNCOMMITED (未提交读)
READ COMMITED (提交读)
REPEATABLE READ (可重复读)
SERIALIZABLE (可重复读)
只要彻底理解了隔离级别以及他的实现原理就相当于理解了ACID里的隔离型。前面说过原子性,隔离性,持久性的目的都是为了要做到一致性,但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的可性保障靠,比如要做到宕机后的恢复,以及错误后的回滚。

那么隔离性是要做到什么呢?隔离性是要管理多个并发读写请求的访问顺序。这种顺序包括串行或者是并行说明一点,写请求不仅仅是指insert操作,又包括update操作。

总之,从隔离性的实现可以看出这是一场数据的可靠性与性能之间的权衡。

可靠性性高的,并发性能低(比如 Serializable)
可靠性低的,并发性能高(比如 Read Uncommited)

READ UNCOMMITTED
在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。

因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。

换句话说,读的操作不能排斥写请求。

优点:读写并行,性能高
缺点:造成脏读

READ COMMITTED
一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。

InnoDB在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制。
但是该级别会产生不可重读以及幻读问题。

什么是不可重读?
在一个事务内多次读取的结果不一样。

为什么会产生不可重复读?
这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

REPEATABLE READ(Mysql默认隔离级别)
在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC。

采用读写锁实现:
为什么能可重复度?只要没释放读锁,在次读的时候还是可以读到第一次读的数据。

优点:实现起来简单
缺点:无法做到读写并行
采用MVCC实现:

为什么能可重复度?因为多次读取只生成一个版本,读到的自然是相同数据。

优点:读写并行
缺点:实现的复杂度高
但是在该隔离级别下仍会存在幻读的问题,关于幻读的解决我打算另开一篇来介绍。

SERIALIZABLE
该隔离级别理解起来最简单,实现也最单。在隔离级别下除了不会造成数据不一致问题,没其他优点。

4.一致性的实现

数据库总是从一个一致性的状态转移到另一个一致性的状态.
下面举个例子:zhangsan 从银行卡转400到理财账户

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400;
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;

假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态。

又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。

假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。

2.sql的顺序和执行顺序:

1.sql语句编写时的顺序

(7) SELECT
(8) DISTINCT <select_list>
(1) FROM
(3) <join_type> JOIN <right_talbe>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) HAVING <having_condition>
(9) ORDER BY <order_by_condition>
(10) LIMIT <limit_number>

2.sql语句执行的顺序

FORM: 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1
ON: 对虚表VT1进行ON筛选,只有那些符合的行才会被记录在虚表VT2中。
JOIN: 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, rug from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。
WHERE: 对虚拟表VT3进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表VT4中。
GROUP BY: 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5.
CUBE | ROLLUP: 对表VT5进行cube或者rollup操作,产生表VT6.
HAVING: 对虚拟表VT6应用having过滤,只有符合的记录才会被 插入到虚拟表VT7中。
SELECT: 执行select操作,选择指定的列,插入到虚拟表VT8中。
DISTINCT: 对VT8中的记录进行去重。产生虚拟表VT9.
ORDER BY: 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10.
LIMIT:取出指定行的记录,产生虚拟表VT11, 并将结果返回。

3.索引

1.索引类型:

1.普通索引 index
2.唯一索引 unique
3.主键索引 pk
4.组合索引 index(k1,k2…)
5.全文索引 fulltext

2.聚簇索引和非聚簇索引(二级索引)

* 如果表设置了主键,则主键就是聚簇索引
* 如果表没有主键,则会默认第一个NOT NULL,且唯一(UNIQUE)的列作为聚簇索引
* 以上都没有,则会默认创建一个隐藏的row_id作为聚簇索引

聚簇索引查询会很快,因为可以直接定位到行记录;而普通索引叶子节点存储主键值,无法获取到完整数据,所以需要再查询一次

3.回表:

回表的标志:explain 语句执行时, extra 字段为:using index
索引下推(在innodb优化)explain 语句执行时, extra 字段为:using index condition(回表的数据在innodb处理,减少与server的交互)

普通索引  需要扫码两遍索引树
(1)先通过普通索引定位到主键值id=5;
(2)在通过聚集索引(id)定位到行记录;
这就是所谓的回表查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。

如下:

CREATE TABLE `user` (`id` varchar(32) NOT NULL COMMENT '主键',`username` varchar(16) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`age` int(11) DEFAULT NULL COMMENT '年龄',`phone` varchar(11) DEFAULT NULL COMMENT '手机号',`email` varchar(50) DEFAULT NULL COMMENT '邮箱',`id_card` varchar(18) DEFAULT NULL COMMENT '身份证',`nick_name` varchar(16) NOT NULL COMMENT '网名/昵称',`area_code` varchar(10) DEFAULT NULL COMMENT '所属地区码',`create_time` datetime NOT NULL COMMENT '创建日期',`update_time` datetime NOT NULL COMMENT '更新日期',`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '可用状态 1可用/0禁用',`version` varchar(255) NOT NULL DEFAULT '0',`open_id` varchar(50) DEFAULT NULL COMMENT '微信开放id',PRIMARY KEY (`id`),KEY `idx_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

查询:

select username,password,phone from user where username = '张三'

我们只是在username字段上建立了索引,但是查询的结果需要获取password,phone字段,这两个字段是没有索引的,mysql会首先根据索引树查询到该列的id,然后又通过id查询一次数据,这就是回表

4.覆盖索引:

覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。
如上,我只有两个索引,一个为主键索引,一个为username索引
执行查询计划:

explain select id,username from user where username = 'love'

结果:

可以看到Extra字段为Using表明使用了覆盖索引,没有执行回表查询(因为治理的username建立了索引,而此索引存储的数据为username+id),如果我还想得到一个mobile的字段,我可以建立一个组合索引index(username,mobile),这样的话就避免了回表查询

5. Explain 查看sql的执行详情

这里进行拓展,说明各参数指的是什么
id字段为SELECT识别符。这是SELECT的查询序列号

1.select_type

表示查询中每个select子句的类型

(1) SIMPLE(简单SELECT,不使用UNION或子查询等)
(2) PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
(3) UNION(UNION中的第二个或后面的SELECT语句)
(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
(5) UNION RESULT(UNION的结果)
(6) SUBQUERY(子查询中的第一个SELECT)
(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
(8) DERIVED(派生表的SELECT, FROM子句的子查询)
(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

2.table

显示这一行的数据是关于哪张表的,有时不是真实的表名字,看到的是derivedx(x是个数字,我的理解是第几步执行的结果)

3.type

表示MySQL在表中找到所需行的方式,又称“访问类型”。
联接类型。下面给出各种联接类型,按照从最佳类型到最坏类型(由上到下性能逐渐变差)进行排序:

system:表仅有一行(=系统表)。这是const联接类型的一个特例。
const:表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!
eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个来自于前面的表的行组合,从该表中读取一行,多表连接中使用unique index或者primary key作为关联条件。这可能是最好的联接类型,除了const类型。
ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取(使用非唯一索引扫描或唯一索引的前缀扫描,返回匹配某个单独值的记录行)。
ref_or_null:该联接类型如同ref,但是添加了MySQL可以专门搜索包含NULL值的行。
index_merge:该联接类型表示使用了索引合并优化方法。
unique_subquery:该类型替换了下面形式的IN子查询的ref: value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。
index_subquery:该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引: value IN (SELECT key_column FROM single_table WHERE some_expr)
range:只检索给定范围的行,使用一个索引来选择行,常见于<,<=,>,>=,between等操作符。
index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。
ALL:对于每个来自于先前的表的行组合,进行完整的表扫描,全表扫描。

4.possible_keys

指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用

该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询

5.Key

key列显示MySQL实际决定使用的键(索引)

如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。

6.key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

不损失精确性的情况下,长度越短越好

7.ref

表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

8.rows

表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数

9.Extra

该列包含MySQL解决查询的详细信息:

Distinct:MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。
Not exists:MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。
range checked for each record (index map: #):MySQL没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用。
Using filesort:MySQL需要额外的一次传递,以找出如何按排序顺序检索行。
Using index:从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息。
Using temporary:为了解决查询,MySQL需要创建一个临时表来容纳结果。
Using where:WHERE 子句用于限制哪一个行匹配下一个表或发送到客户。
Using sort_union(…), Using union(…), Using intersect(…):这些函数说明如何为index_merge联接类型合并索引扫描。
Using index for group-by:类似于访问表的Using index方式,Using index for group-by表示MySQL发现了一个索引,可以用来查 询GROUP BY或DISTINCT查询的所有列,而不要额外搜索硬盘访问实际的表。

6.覆盖索引可以做的sql优化:

1.全表count查询优化:
删除之前我们在username上建立的索引

执行如下sql

select count(username) from user


再给username字段加上索引:

执行sql

select count(username) from user


count查询就走的覆盖索引

2.列查询回表优化

select id,username,mobile where username=‘shenjian’;

这个例子不再赘述,将单列索引(username)升级为联合索引(username, mobile ),即可避免回表。

3.分页查询优化

select id,username,mobile order by username limit 500,100;

将单列索引(username)升级为联合索引(username, mobile),也可以避免回表。

5.mysql索引创建原则:

1.查询频率高的字段创建索引
2.对排序、分组、联合查询频率高的字段创建索引;当单个索引字段查询数据很多,区分度都不是很大时,则需要考虑建立联合索引来提高查询效率。但是需要根据sql的执行顺序来做索引的优化,避免索引失效的问题
3.索引的数目不宜太多,一般控制在5-6个
4.需要将多个列设置索引时,可以采用多列索引
5.字段不重复的可以选用唯一性索引
6.尽量使用前缀来索引(尽量使用数据量少的索引)TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
7.删除不用或者很少用的索引
8.最左前缀匹配原则,非常重要的原则。
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
9 .=和in可以乱序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
10.尽量选择区分度高的列作为索引(比如性别这个玩意一般只有两种,所以就不建议在这个字段上建立索引)
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
11.索引列不能参与计算,保持列“干净”。(也就是不对列表中的列数据使用函数操作,这回对所有数据都进行函数操作以后再比较)
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
12.尽量的扩展索引,不要新建索引
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可

6.mysql索引失效场景:

1.like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。

非覆盖索引情况下:
执行:

explain select id,username,phone from user where username like  '陈8%'

结果:查询类型type为range,索引正常

执行:

explain select id,username,phone from user where username like  '%陈8%'

索引失效,查询类型type为all

覆盖索引情况下:

explain select id,username from user where username like  '%陈8%'

type的结果为index;全表扫描的意思就是要把表中所有数据过一遍才能显示数据结果,索引扫描就是索引,只需要扫描一部分数据就可以得到结果, 打个比方吧,在新华字典中,如果没有拼音或笔画索引,当我们查找“做”这个字就要从字典第一页一次往后查,一直插到Z开头的部分才能找到,即使找到也不确定后面是不是还有(假定字典是无序状态的),因此还得往后找,知道正本字典翻完,才确定“哦,原来刚才找到的那个记录就是想要的结果了”。索引扫描的意思就是我们预先知道“做”这个字在拼音的Z区域,然后根据前面目录查看"zuo"这个拼音在那一页,然后直接翻到那一页就能找到我们要的结果了,这样就能大大减少查询的时间

2.or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效

3.组合索引,不是使用第一列索引,索引失效。
如果我创建组合所索引的是(name,phone) 执行如下

这个会走索引,但是查询效率并不高 只是比select * from user where phone = ’ ’ 高很多而已
select name from user where phone = ‘123’

索引下推:将server中过滤的数据下推到innodb做处理后再给server这样引擎和innodb的交互次数就少了,就可以提升效率

4.数据类型出现隐式转化。如varchar不加单引号的话可能会自动转换为int型,使索引无效,产生全表扫描。

5.在索引列上使用 IS NULL 或 IS NOT NULL操作。(此说法不完全可靠)参考这个文章

6.在索引字段上使用not,<>,!= 不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。

7.对索引字段进行计算操作、字段上使用函数。

8.当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效。

7.sql在mysql中是如何执行的

1.MySQL 基本架构概览

介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。

连接器: 身份认证和权限相关(登录 MySQL 的时候)。
查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
优化器: 按照 MySQL 认为最优的方案去执行。
执行器: 执行语句,然后从存储引擎返回数据。

通俗来讲就是MySQL 主要分为 Server 层和存储引擎层:

Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。

Server 层基本组件介绍

  1. 连接器
    连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。

主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。

  1. 查询缓存(MySQL 8.0 版本后移除)
    查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。

连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。

MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。

所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。

MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。

  1. 分析器
    MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:

第一步,词法分析,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。

第二步,语法分析,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。

完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。

  1. 优化器
    优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。

可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。

  1. 执行器
    当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。

2.分析

一条 sql 语句是如何执行的呢?一般来讲,sql大致可以分为两类,一种是查询,一种是更新(增加,更新,删除)

先看查询的sql是如何执行的:

select * from user where username = 'love' and area_code = '5131'

分析下这个语句的执行流程:
1.先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。

2.通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 user ,需要查询所有的列,查询条件是这个表的 username = ‘love’ 和area_code = ‘5131’。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。

3.接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:
a. 先查询用户表username为love的用户,然后判断area_code是否为5131。
b. 先找出用户表中area_code为5131的用户,然后再查询username为love的用户。

那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。
进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。

接下来我们看看一条更新语句如何执行的:

update user set area_code = '5101' where username = 'love'

其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 binlog(归档日志) ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 redo log(重做日志),我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:

先查询到love这一条数据,如果有缓存,也是会用到缓存。
然后拿到查询的语句,把 area_code 改为 5101,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
更新完成。
这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?

这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。

并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?

先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:

判断 redo log 是否完整,如果判断是完整的,就立即提交。
如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。

3.总结:

MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
SQL 等执行过程分为两类:(以下结果有待商榷)
查询过程如下:权限校验----》查询缓存----》分析器----》优化器----》权限校验----》执行器----》引擎
对于更新等语句执行流程如下:分析器----》权限校验----》执行器----》引擎----》redo log prepare----》binlog----》redo log commit

8.SQL优化:

排查步骤:
1.mysql中可以开启slow log(慢查询日志)
2.根据对业务的影响解决最慢的sql
3.使用explain select语句查看执行情况
4.进行索引调整或者sql修改

优化操作:
1.创建合适的索引
2.避免索引失效:(避免全表扫描)
1.避免使用 like ‘%条件’(虽然覆盖索引type为index,但是不建议这么写),而like ‘条件%’ type为range ,rang优于index
2.在索引字段做运算或者使用函数的情况:
在create_time上建立索引

执行

explain select * from user where YEAR(create_time)<2020

以上使用YEAR相当于对每一列create_time都做了一次运算,导致全表扫描,就不会走索引 可以看到走得是全表扫描改为以下方式:

  explain select * from user where create_time<'2020-01-01 00:00:00'


可以看到走得索引,type为range

注意:同样的sql可能会因为你查询范围不同 而选择走索引或者不走索引

如下:

select * from user where create_time<'2020-01-01 00:00:00'

可以看出满足这个日期的只有一条数据

那如果把<改为>呢?会不会走索引呢?
执行

explain select * from user where create_time>'2020-01-01 00:00:00'

可以看到走得是全表扫描
什么原因呢:
执行sql

select * from user where create_time>'2020-01-01 00:00:00'


可以看到记录有800条,而我们的表数据一共801条。这个走不走索引似乎是与你查询的结果与你表中的中数据量的比值决定的。

此处摘录别人的经历:请参考时间范围查询索引失效

3.范围列索引(范围查询的and条件只会走其中一个索引)
在update_time字段上添加索引:

执行sql:

explain select * from user where update_time <'2021-04-03 00:00:00' and create_time<'2020-01-01 00:00:00'

结果:

走得是create_time的索引,而不是update_time?不是update_time在前面吗?

我的理解:(不一定对)
分开来执行两个语句:(当然,这里可能也需要考虑上面的条件查询结果占总记录数很大而导致索引失效的问题,我这里的数据是经过考虑的,我这里已经确保这两个条件都是走得索引)

执行sql:

explain select * from user where update_time <'2021-04-03 00:00:00' and create_time<'2020-01-01 00:00:00'

结果:

发现他走的是create_update的索引;而没有upate_time的索引;范围查询可能只会走一个索引

4.隐式转换导致的索引失效问题:
执行sql:

explain select * from user where username = 'xxx'

可以看到走的类型为ref
如果是下面这句sql呢

explain select * from user where username = 123

以下这个sql走的就是全表扫描,上面就是隐式转换导致的索引失效问题

5.单表索引数量不超过5个,索引本身也是一个数据结果,不仅会占用一部分的内存资源,也会在对数据进行修改的时候涉及到索引重建的问题。

6.避免使用null值,老版本的mysql列有null值是无法使用索引的,5.7以后支持了null使用索引,但是会有问题,所以还是避免使用null值(通过设置默认值或者说非空约束)

7.避免负向条件,在sql中减少或避免使用!=和<>(<>等价于不等于而不是说>或者<)

8.避免使用or条件,老版本使用or是不会走索引的
将上面的and条件改为or条件

 explain select * from user where update_time <'2021-04-03 00:00:00' or create_time<'2020-01-01 00:00:00'


发现使用了type为index_merge,性能比之前的range还高
注意:使用and条件时,只走了其中一个索引,而使用or条件时,反而走了两个索引

9.减少使用in和not in语句
可以使用join连接查询

10.like查询避免使用 like ‘%关键字’ 或者 ‘%关键字%’
不得已的时候可以考虑覆盖索引的方式来做优化(可能提升并不高,也可能没有提升,这个我不确定)

11.避免对列进行计算,可以转为对常量进行计算

select * from user where age*2 = 48;

这种应当被优化成

select * from user where age = 48/2
  1. 避免对列进行函数运算
    如:
  explain select * from user where substring(username,1,4) = 'love'

应当修改为

 explain select * from user where username like 'love%'

其他的函数操作以此类推

13.组合索引合理使用,提高效率,避免索引失效
可以尝试一下在一个复杂查询中使用这种索引去体验一下

14.使用exists去代替in语句

select num from a where num in(select num from b)

改为
select num from a where num exists(select 1 from b where num = a.num)

15.索引建立时,避免在重复性较大的列上建立索引
如性别字段,我国承认的是两种,这个字段就没有比较建立索引了

16.表格中字段选择:
数值性的数据使用数字类型而不用字符串类型,数值类型会直接比较,而字符串类型是一个一个对比,性能不如数值型的直接比较
使用varchar而不是用char类型:char不管数据多少都会用那么多的空间,并且存储数据varchar的字段长<使用char类型的字段长,搜索性能相对高一点。
Text等长文本避免使用索引

17.避免使用select * from ;使用具体的字段代替,且尽量只返回需要的数据

18.表连接查询不超过5张表

19.小表驱动大表:
反例:
例: user表10000条数据,class表20条数据

select * from user u left join class c on u.class_id=c.id

这样则需要用user表循环10000次才能查询出来,而如果用class表驱动user表则只需要循环20次就能查询出来。
以上user为大表,class为小表,这种写法应该是错误的;转而使用class去驱动user表

在数据库查询中
SELECT * FROM 小表 INNER JOIN 大表 ON 小表.id=大表.id
效率高于
SELECT * FROM 大表 INNER JOIN 小表 ON 小表.id=大表.id
前者时间更短!

前表查询出数据需要一条一条的加入到join_buffer中,这需要IO操作,比较耗时,因此如果前表比较小,那么效率就高,这是小表驱动大表的一个主要原因;
将join_buffer中的数据和后表中的数据进行匹配,如果连接得字段可以使用索引,那么效率就更高了

9.Innodb为什么使用B+

hash:查找快,但是不适合范围查找

平衡二叉树:避免的左倾和右倾,但是数据量大的时候,树高会很高,也就是IO次数会很多

B Tree(多路平衡查找树):相比平衡二叉树,树高是降低了,但是还是不适合范围查询,范围查询需要遍历所有数据

B+Tree:将所有数据都放到叶子节点,且叶子节点形成一个列表(可以做范围查询),非叶子节点只放键值,每个数据叶中的有效数据就多了,可以减少IO次数。稳定性更高,同一个sql查询到数据的深度都是相同的

为什么B+树比B树更适合做索引
 B树也是多叉树结构的自平衡的树,而且B+树是从B树演化而来的,那么为什么不使用B树呢?从结构比较来看,B树相比B+树的一个主要区别就在于B树的分支节点上存储着数据,而B+树的分支节点只是叶子节点的索引而已。根据这个差别可以得出以下结论:

磁盘IO读写次数相比B树降低了
  在B+树中,其非叶子的内部节点都变成了key值,因此其内部节点相对B 树更小。如果把所有同一内部节点的key存放在同一盘块中,那么盘块所能容纳的key数量也越多。一次性读内存中的需要查找的key值也就越多。相对来说IO读写次数也就降低了。
每次查询的时间复杂度是固定的
  在B+树中,由于分支节点只是叶子节点的索引,所以对于任意关键字的查找都必须从根节点走到分支节点,所有关键字查询路径长度相同,每次查询的时间复杂度是固定的。但是在B树中,其分支节点上也保存有数据,对于每一个数据的查询所走的路径长度是不一样的,所以查询效率也不一样。
遍历效率更高
  由于B+树的数据都存储在叶子节点上,分支节点均为索引,方便扫库,只需扫一遍叶子即可。但是B树在分支节点上都保存着数据,要找到具体的顺序数据,需要执行一次中序遍历来查找。所以B+树更加适合范围查询的情况,在解决磁盘IO性能的同时解决了B树元素遍历效率低下的问题。
因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低。

Java面试之Mysql相关推荐

  1. Java面试复习---MySQL(狂神版)

    Java面试复习---MySQL(狂神版) 前言 1.初始MySQL 1.1.为什么学习数据库 1.2.什么是数据库 1.3.数据库分类 1.4.MySQL简介 1.5.安装MySQL 1.6.安装S ...

  2. 准备Java面试?mysql用户远程访问授权

    第1章 初识Redis 初识Redis,带领读者进入Redis的世界,了解它的前世今生.众多特性.应用场景.安装配置.简单使用,最后对Redis发展过程中的重要版本进行说明,可以让读者对Redis有一 ...

  3. 【Java面试】MySQL

    文章目录 一.数据库基础知识 1.什么是SQL? 2.什么是MySQL? 3.数据库三大范式是什么? 4.mysql有关权限的表都有哪几个? 5.MySQL的binlog有几种录入格式?分别有什么区别 ...

  4. Java面试必备MySQL知识(一)

    查看MySQL提供的所有存储引擎命令:show engines MySQL的默认存储引擎是InnoDB,它是事务性存储引擎 MyISAM与InnoDB的区别 是否支持行级锁:MyISAM只具有表级锁, ...

  5. 征服Java面试官!mysql索引树结构

    美团技术一面20分钟 晚7点,因为想到下周一才面试,我刚准备出去打个羽毛球,北京的电话就来了.面试官各种抱歉,说开会拖延了. 1.自我介绍 说了很多遍了,很流畅捡重点介绍完. 2.问我数据结构算法好不 ...

  6. 不可多得的干货!面试讲不清MySQL索引底层,Java面试真题精选

    前言 二面大概50分钟,问的东西很全面,需要做充足准备,就是除了概念以外问的有点懵逼了(呜呜呜).回来之后把这些题目做了一个分类并整理出答案(强迫症的我狂补知识~)分为spring,jvm,并发编程等 ...

  7. 【2022最新Java面试宝典】—— MySQL面试题(40道含答案)

    目录 1.MySQL 中有哪几种锁? 2.MySQL 中有哪些不同的表格? 3.简述在MySQL 数据库中 MyISAM 和InnoDB 的区别 4.MySQL 中InnoDB 支持的四种事务隔离级别 ...

  8. Java岗面试:mysql破解版百度云

    所以,我认为在你选择之前不妨好好想想什么是Java?你适不适合从事这份工作? Java开发是近20多年来最热门的编程语言,就业市场确实比较大,入门的难度也比C和C++要低,结合各方面来说,你选择Jav ...

  9. Github 一夜爆火:这份金九银十 Java 面试手册我给跪了

    这几天给筒子们整理了一份<Java面试手册>,106页,目前大约6万字左右,初衷也很简单,就是希望在面试的时候能够帮助到大家,减轻大家的负担和节省时间. 废话不多说,本手册目前为第一版,后 ...

最新文章

  1. 厚积薄发的90后:读博前三年零文章,后期发力产出11篇一作,现任985高校博导...
  2. spring mvc-REST
  3. java 进程消失_Java进程诡异消失问题
  4. android string.xml前后加空格的技巧
  5. c++ map用法_Pandas数据处理三板斧——map、apply、applymap详解
  6. android filehelper,为AndroidStudio开发mvp插件(MvpHelper)
  7. sketch设置字体技巧(二)---通过组合法重新组建字体
  8. 基于Jquery的图片自动分组且自适应页面的缩略图展示特效
  9. Harmony OS — ProgressBar垂直、水平进度条
  10. JavaScript---去除字符串中的空格(五种方式,总有一种适合你)
  11. 手机群控还有这种事半功倍的操作?快来看强大的Rest API脚本功能
  12. WinRAR 去除广告弹窗,简单4步亲测有效!
  13. Win7电脑无法安全删除硬件并弹出媒体的解决方法
  14. pandas_数据处理分析基本
  15. 推荐:基于.NET写的本地搜索工具-EverythingToolbar
  16. 符合FDA标准的邮件安全证书(S/MIME)有哪些?
  17. java计算机毕业设计体育城场地预定系统前台源码+系统+数据库+lw文档+mybatis+运行部署
  18. 基于微信小程序的家政服务预约系统的设计与实现
  19. 分区和分片的区别_MySql分表、分库、分片和分区知识点介绍
  20. 单文件程序制作一键通三合一 v5.10 杏雨梨云版

热门文章

  1. PTA 7-228 加法口诀表
  2. 微电商圈地,传统电商3年死光【强烈推荐】
  3. python跑模型是什么意思_RFM模型是什么,我用python带你实战!
  4. VBA RemoveDuplicates方法去重复项
  5. Python查询手机号码归属地几种方法
  6. MSP430F248TPMR 德州TI 超低功耗微控制器 封装LQFP
  7. 安装、卸载TCP/IP协议
  8. 5.4.2_利用图块复制技术来处理动画背景
  9. steam 代发游戏系统
  10. vue3.0 + ts H5拍照组件