mysql 相关内容

mysql基本架构

1)、连接器
连接器负责跟客户端建立连接,获取权限、维持和管理连接
用户名密码验证、查询权限信息,分配对应的权限
可以使用 show processlist 查看现在的连接
如果太长时间没有动静,就会自动断开,通过 wait_timeout 控制,默认8小时
连接可以分为两类:长连接:推荐使用,但是要周期性的断开长连接   短链接

2)、查询缓存(mysql 8 已经废弃)
当执行查询语句的时候,会先去查询缓存中查看结果,之前执行过的sql语句及其结果可能以key-value的形式存储在缓存中,如果能找到则直接返回,如果找不到,就继续执行后续的阶段
不推荐使用查询缓存: 
2.1)查询缓存的失效比较频繁,只要表更新,缓存就会清空
2.2)缓存对应新更新的数据命中率比较低

3)、分析器(sql语句解析成 AST(抽象语法树))
词法分析:Mysql需要把输入的字符串进行识别每个部分代表什么意思
例如:把字符串 T 识别成 表名 T,把字符串 ID 识别成 列ID
语法分析:根据语法规则判断这个sql语句是否满足mysql的语法,如果不符合就会报错“You have an error in your SQL synta”

4)、优化器
在具体执行SQL语句之前,要先经过优化器的处理,当表中有多个索引的时候,决定用哪个索引,当sql语句需要做多表关联的时候,决定表的连接顺序等等
不同的执行方式对SQL语句的执行效率影响很大
RBO:基于规则的优化
CBO:基于成本的优化

mysql 主从同步原理

1)、复制类型(binlog-> mysql server 实现的)

1.1)、SBR(STATEMENT)模式

基于语句,主库将每一条会修改的sql语句记录到 binlog 中

优点:不需要记录每一条sql和每一行数据变化,减少 binlog 日志量,节约IO,提高性能

缺点:某些情况下会导致 master 和 slave 的数据不一致(如 sleep()、last_insert_id()、user_defined function(udf)等会出问题)

1.2)、RBR(ROW)模式

基于行数据,不记录sql语句,只记录哪条数据被修改了

优点:不会出现某些特定情况下的存储过程、function、触发器调用无法被正确复制的问题

缺点:会产生大量的日志,尤其是 alter table 的时候

1.3)、MBR(MIXED)模式

SBR 和 RBR 两种模式的混合使用,一般的复制使用 SBR 模式保存 binlog,对于 SBR 模式无法复制的操作使用 RBR 模式保存 binlog,mysql会根据执行的sql选择日志保存方式

2)、工作原理

2.1)、开启bin log 功能后,master 操作发生了变化就会记录到 binlog(binary log) 二进制日志中

2.2)、slave 会启动一个单独的 IO thread 连接 master,请求 master 从指定位置更新的 bin log 内容

2.3)、master 会创建一个 log dump 线程用于发送 binlog 内容,在读取 binlog 过程中 master 会对该文件加锁,读取完成之后释放锁

2.4)、slave 获取到 binlog 日志后保存到自己的 reply log(中继日志) 日志文件中

2.5)、slave 有一个 sql thread 定时检查 reply log 是否有变化,有变化就更新数据

mysql 主从复制延迟(5.6 以后版本解决)

1)、binlog 组提交 (对于同一个表记录修改的事务不能分到一个组中)

2)、GTID 并行复制,分组将binlog 刷新到磁盘中,事务id(GTID -> UUID+TID,TID就是事务的数量)和分组id(last_committed、sequence_number)记录到了 binlog 日志中

3)、MTS(multi thread slave,多线程 sql thread 处理 reply log) 技术并行复制,coordinator 线程读取reply log后,负责管理分发要执行的事务到多个 work thread(slave_parallel_workers 变量设置数量) 中

每个 work thread 都定义一个 hash 表,用来保存当前这个 worker 正在执行的事务所涉及到的表。hash表的 key值 按照不同的粒度需要存储不同的值:

按库分发:key值是数据库的名字
​按表分发:key值是库名+表名
​按行分发:key值是库名+表名+唯一键

coordinator 分发事务要遵循以下原则:
3.1)、事务不可拆分,必须整体分配到某个 work thread 中
3.2)、对于修改同一个表的记录(同库)的事务,必须都分配到同一个 work thread 中

双1设置解决数据安全问题(丢失)

1)redo log:
innodb_flush_log_at_trx_commit 属性
0(每次写到服务缓存,一秒钟刷写一次)
1(每次事务提交都刷写一次磁盘)
2(每次写到os缓存,一秒钟刷写一次)

2)、binlog: 
sync_binlog  属性
0(每次提交事务都只write,不fsync)  
1(每次提交事务都执行fsync)  
2(每次提交事务都write,但积累N个事务后才fsync)

mysql select查询过程

查询主要经过了 连接处理、sql 语法解析(sql语法树)、sql优化、sql执行 这四个步骤

sql优化

优化分为 子查询优化、条件化简、语义优化、多表连接优化、外连接优化、嵌套连接优化、视图重写、谓词重写等

mysql事务原理

mysql的事务是通过 redo log 、undo log 、MVCC(乐观锁)和 锁(悲观锁)来实现的

1)、redo log(InnoDB 存储引擎实现)

定义:重做日志,用来实现事务的持久化。 当发生数据修改的时候,innodb引擎会先将记录写到redo log中,并更新内存,此时更新就算是完成了,同时innodb引擎会在合适
的时机将记录操作到磁盘中。redo log是固定大小的,是循环写(checkpoint、writePos 指针)的过程。它包含 redo log buffer(重做日志缓冲,在内存中)和 redo log(重做日志文件,在磁盘上),事务没有提交前将事务操作存入 redo log buffer 缓存中 ,当事务成功提交后,则会将 redo log buffer 中的缓存事务持久化到redo log 文件中

作用:用于持久化已提交事务,防止事务信息丢失,用于恢复数据保障

mysql为了提高性能不会每次修改都直接实时同步到磁盘中,而是先缓冲到 Buffer Pool(缓冲池) 中,然后使用后台线程定时做Buffer Pool与磁盘的同步,这里就会有事务丢失的问题,而 redo log的解决了此问题,下图给出了 redo log 为什么不会造成io瓶颈的问题


2)、undo log(InnoDB 存储引擎实现)

定义:回滚日志,每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log 中,和 redo log(记录修改后的数据信息) 正好相反

作用:用于回滚数据,保障未提交事务的原子性

注意:undo log是逻辑日志,可以理解为:
当delete一条记录时,undo log中会记录一条对应的insert记录
当insert一条记录时,undo log中会记录一条对应的delete记录
当update一条记录时,它记录一条对应相反的update记录

如果系统错误或者发生rollback操作而需要回滚的话,可以根据 undo log 信息来进行回滚到被修改前的状态

mysql一个整体事务处理如下图所示:

3)、binlog(mysql server 服务端的日志文件)
binlog 是 server 层的日志,主要做mysql功能层面的事情
与redo日志的区别:
1)redo是innodb独有的,binlog是所有引擎都可以使用的
2)redo是物理日志,记录的是在某个数据页上做了什么修改
3)redo是循环写的,空间会用完,binlog是可以追加写的,不会覆盖之前的日志信息
binlog是逻辑日志,记录的是这个语句的原始逻辑,采用追加写的方式记录所有的逻辑
一般在企业中数据库会有备份系统,可以定期执行备份,备份的周期可以自己设置
恢复数据的过程:
1)找到最近一次的全量备份数据
2)从备份的时间点开始,将备份的binlog取出来,重放到要恢复的那个时

mysql数据更新流程

执行流程:
1、执行器先从引擎中找到数据,如果在内存中直接返回,如果不在内存中,查询后返回
2、执行器拿到数据之后会先修改数据,然后调用引擎接口重新吸入数据
3、引擎将数据更新到内存,同时写数据到redo中,此时处于prepare阶段,并通知执行器执行完成,随时可以操作
4、执行器生成这个操作的binlog
5、执行器调用引擎的事务提交接口,引擎把刚刚写完的redo改成commit状态,更新完成

redo log 的两阶段提交
1)、先写redo log 后写 binlog:假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。
2)、先写binlog后写redo log:如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同

MVCC乐观锁

定义:MVCC (Multi-Version Concurrency Control) 是一种多版本并发控制协议,只有在 InnoDB 存储引擎下存在,指的就是在使用 READ COMMITTD、REPEATABLE READ 这两种隔离级别的事务在执行普通的 select 操作时访问记录的版本链的过程,这样子可以使不同事务的 读 - 写 、 写 - 读 操作并发执行,从而提升系统性能。READ COMMITTD、REPEATABLE READ 这两个隔离级别的一个很大不同就是:生成 ReadView 的时机不同,分别如下:
READ COMMITTD: 在每一次进行普通 SELECT 操作前都会生成一个 ReadView 
REPEATABLE READ: 只在第一次进行普通 SELECT 操作前生成一个 ReadView ,之后的查询操作都重复使用这个 ReadView 就好了

作用:实现一致性非锁定读,这就有保证在同一个事务中多次读取相同的数据返回的结果是一样的,解决了不可重复读的问题,实现事务的隔离性,通过版本号避免同一数据在不同事务间的竞争

原理:通过在每行记录后面增加两个隐藏的数据列来实现,这两个列一个保存的是行的事务版本号(可以理解为创建时间),一个保存的是行的删除版本号(可以理解为删除时间)

每开始一个新的事务,事务版本号就会递增,行的系统版本号都会更新成事务的当前版本号,用来和查询到的每行记录做版本号比较

select: 查询出来的记录必须符合以下两个条件:

1)、行版本号小于等于当前事务版本号,这样可以确保事务读取的行要么是在事务开始之前存在的,要么就是事务自身插入或者修改过的

2)、无删除版本号或者删除版本号大于当前事务版本号,这样可以确保事务是在删除操作之前进行的,读取的数据当然也是没有被删除的

insert: InnoDB 为每一个新增的行保存事务版本号作为行版本号

delete: InnoDB 为每一个删除的行保存事务版本号作为行删除标识(删除版本号)

update: InnoDB 为每一个新增的行保存事务版本号作为行版本号,同时将当前事务的版本号作为原来的行的删除版本号,使大多数操作都不用加锁。 缺点是每行记录都需要额外的存储空间来存储删除了的数据行

MVCC 只适用于 mysql 中的 RC(读已提交)、RR(可重复读/可幻读)隔离级别,不适合 RU(读未提交)和 Serializable(串行化)隔离级别,原因如下:

1)、MVCC创建的版本号只有在事务提交之后才会产生,所以必须是 RC,而不能是 RU

2)、Serializable 隔离级别时mysql会对整张表加锁,此时自然不会存在事务并发问题

MVCC 主要作用于事务的,有行锁控制的数据库模型,主要用于读并发控制,因为更新时都会加锁,使用 MVCC 并没有太大意义

InnoDB 通过 MVCC(多版本并发控制) 解决不可重复读问题,加上间隙锁(并发控制)解决幻读问题

InnoDB 的 RR(可幻读)隔离级别实际上实现了串行化的效果,而且还保留了好的并发性能

事务的隔离性是通过乐观锁(MVCC)实现的,而事务的一致性、原子性和持久性是通过事务日志实现的

mysql锁

1)、自增锁(auto_increment),mysql innodb_autoinc_lock_mode 默认值 1

innodb_autoinc_lock_mode = 0:一律采用 AUTO-INC 锁(执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉,这样可以保证一个语句中分配的递增值是连续的)

innodb_autoinc_lock_mode = 2:一律采用轻量级锁(在为插入语句生成AUTO_INCREMENT修饰的的值时获取一下这个轻量级锁,然后生成本次插入语句需要用到的AUTO_INCREMENT列的值之后,就把该轻量级锁释放掉,并不需要等到整个插入语句执行完才释放锁)

innodb_autoinc_lock_mode = 1:两种方式混着来(也就是在插入记录数量确定时采用轻量级锁,不确定时使用 AUTO-INC 锁)

mysql innodb_autoinc_lock_mode 默认 1

不过当 innodb_autoinc_lock_mode 值为 2 时,可能会造成不同事务中的插入语句为 AUTO_INCREMENT 修饰的列生成的值是交叉的,在有主从复制的场景中是不安全的

2)、锁的模式:IS(共享意向锁)、IX(独占意向锁)、S(共享锁)、X(独占锁)

IS:对InnoDB存储引擎的表的某些记录加 S 锁 之前,那就需要先在表级别加一个 IS 锁

IX:对InnoDB存储引擎的表的某些记录加 X 锁 之前,那就需要先在表级别加一个 IX 锁

IS 锁 和 IX 锁 的使命只是为了后续在加表级别的 S 锁 和 X 锁 时判断表中是否有已经被加锁的记录,以避免用遍历的方式来查看表中有没有上锁的记录,IS 和 IX 是兼容的,IX 和 IX 是兼容的

3)、锁类型:表锁(IS、IX),行锁(S、X)

行级锁:LOCK_REC_NOT_GAP(Record Locks 正经记录锁,即一般锁)、LOCK_GAP(Gap Locks 间隙锁)、LOCK_ORDINARY(next-key 锁,等同于 LOCK_REC_NOT_GAP + LOCK_GAP)、LOCK_INSERT_INTENTION(Insert Intention Locks 插入意向锁)

Record Locks:仅仅把一条记录锁上

Gap Locks:间隙锁,例如数据库中有1、2、6、9四条记录,此时 T1 在6记录上加 gap 锁后,T2 是无法在(2,6)之间插入任何记录的,gap 锁 仅仅是为了防止插入幻影记录而提出的(即解决幻读),如果你对一条记录加了gap 锁 (不论是 共享 gap 锁 还是 独占 gap
锁 ),并不会限制其他事务对这条记录加 LOCK_REC_NOT_GAP 或者继续加gap 锁

Next-Key Locks:不仅锁住某条记录,又会阻止其他事务在该记录前边的 间隙 插入新记录,next-key 锁 的本质就是一个 Record Locks 和一个 gap 锁 的合体

Insert Intention Locks:一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap 锁 或者 next-key 锁(next-key 锁 也包含gap 锁 ),如果有的话,插入操作需要等待,直到拥有gap 锁 的那个事务提交

sql语句优化过程

slow_query_log:慢查询日志
long_query_time:查询时间 
slow_query_log_file : 慢查询日志存储路径

union :去除重复列(distinct)   union all :包含所有的列

1)、打开慢查询日志,监控到慢sql

2)、分析sql是否可以加索引

创建索引规则如下:

2.1)、索引并非越多越好,大量的索引不仅会占用很大的磁盘空间,而且会影响 insert、update 和 delete 的性能

2.2)、对于频繁更新的表尽量少见索引,如果要建索引的话(经常要查询的列),索引的列尽可能的少

2.3)、对于数据少的表不要建索引,数据较少,查询时间可能比遍历索引还要短,索引可能被不会产生优化效果

2.4)、频繁排序或者分组的列建立索引,如果排序的列有多个,可以建立组合索引,当查询条件中有范围查询时,范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列,例如 组合索引 (a,b,c), where  a = 10 and b > 10 and c = 20,  c 不会用到索引了

2.5)、区分度低(不同值很少的字段,例如男、女)的字段不要建索引,区分度 80% 以上建索引,区分度 = count(distinct(列))/count(*)

2.6)、extra 中出现 using filesort 则说明没有使用索引排序

2.7)、union all 、or 、in 推荐使用 in

2.8)、select 范围查找时,可以考虑转换为 id 区间查找

2.9)、name like '%abc' 可以优化为 name1 字段存入 abc%,使用 name1 like 'abc%' 查询

2.10)、name like '%abc%' 可以优化为2步查找(先找id,再根据id查询记录):select * from t where id in(select id from t where name like '%abc%')

2.11)、select count(*) 数据量大时可以创建一个二级索引(字段区分度大&&字段小),原因如下:
主键索引是聚簇索引(包含了KEY,除了KEY之外的其他字段值,事务ID和MVCC回滚指针)所以主键索引一定会比二级索引(包含KEY和对应的主键ID、页号)大,也就是说在有二级索引的情况下,一般COUNT()都不会通过主键索引来统计行数,在有多个二级索引的情况下选择占用空间最小的

2.12)、分页查询优化:SELECT  * FROM a a1 RIGHT JOIN  ( SELECT  id FROM a a2 WHERE 各种条件 LIMIT 0,10)  temp ON temp.主键 = a1.主键

2.13)、使用覆盖索引查询

2.14)、使用 id 自增主键,字符串可以作为业务主键索引,id 自增主键可以减少页分裂,提高插入效率

3)、使用 explain 命令分析sql是否用到了索引,如果没有用到的话则修改sql用上索引

4)、索引也无法优化的情况下,使用代码将复杂查询拆分成多次查询

5)、使用ES搜索引擎做查询

索引分类

物理存储角度:聚簇索引和非聚簇索引

数据结构角度:B+树索引、hash索引、FULLTEXT全文索引、R-Tree索引

逻辑角度:主键索引、普通索引、唯一索引、联合索引、空间索引(空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON)

hash索引

hash索引使用某种哈希算法将键值转换为新的哈希值。不需要像B +树那样从根节点到叶节点逐步搜索。只需要一种哈希算法,就可以立即找到对应的位置,速度非常快

优点:如果只是查询某个条件的等价查询(hash冲突低),hash索引非常高效

缺点:

1)、hash索引无法支持范围查询

2)、hash索引无法使用索引排序和模糊匹配

3)、hash索引不支持联合索引的最左匹配规则

4)、键值大量冲突的情况下,hash索引效率很低,需要遍历链表来匹配查找数据

mysql某表建了多个单索引,查询多个条件时如何走索引的?

这里主要就是看优化器是如何选择索引的,优化器会评估出使用哪个索引最优,然后执行查询

mysql在优化器中有一个 Range 优化器,负责进行范围查询的优化,该优化器计算成本有两种方式: index dive 和 index statistics,它们是对开销代价的估算方法

一、index dive

统计速度慢但是能得到精准的值

计算成本方式:COST = CPU COST + IO COST

COST 是总花销时间,CPU COST 是处理返回记录数据花销时间,IO COST 是操作IO所花销的时间

mysql会对查询条件中用到的每个索引进行上述成本估算,最后以最小成本来选择索引执行sql

二、index statistics

统计速度快但是数据不精准

计算成本方式:SHOW INDEX FROM tbl_name [FROM db_name]

查询出来的结果中有一个列名为 Cardinality,表示索引列中不重复值得个数(即唯一值得个数),这个值越大说明数据区分度越高,优化器会选择 Cardinality 值最高的索引,反之优化器会认为这个索引对语句查询没有太大帮助而放弃选择该索引

在某些情况下mysql执行 index dive 的成本太大,因此优化器会选择以index statistics方式进行估算成本

Buffer Pool(缓冲池) 优化

1)、调节 innodb_buffer_pool_size 大小,可以容纳更多的页数据,提高查询性能

2)、调节 LRU 链表的  young 区域 和  old 区域占比大小

young 区域 :热点数据

old 区域:冷数据,某个页面在初次加载到 Buffer Pool 中的某个缓存页时,该缓存页对应的控制块会被放到 old 区域的头部

innodb_old_blocks_pct:默认情况下,old区域在LRU 链表 中所占的比例是37%,可以调节大小

innodb_old_blocks_time:默认值是 1000 ms,在对某个处在old区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该页面就不会被从 old 区域移动到 young 区域的头部,否则将它移动到 young 区域的头部(针对短时间内大量使用频率非常低的页面优化)

3)、innodb_buffer_pool_instances,设置 Buffer Pool 多个实例,提高多线程并发访问速度,当 innodb_buffer_pool_size 的值小于 1G 的时候设置多个实例是无效的, InnoDB 会默认把 innodb_buffer_pool_instances  的值修改为 1,建议当 Buffer Pool 大小或等于 1G 的时候设置多个Buffer Pool实例

4)、 innodb_buffer_pool_chunk_size :默认 128M,代表 InnoDB 向操作系统申请的一片连续的内存空间的大小,它的值只能在服务器启动时指定,在服务器运行过程中是不可以修改的

配置 Buffer Pool 注意事项:

1)、innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数(这主要是想保证每一个Buffer Pool实例中包含的chunk数量相同)

2)、如果在服务器启动时,innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的值已经大于 innodb_buffer_pool_size 的值,那么innodb_buffer_pool_chunk_size的值会被服务器自动设置为innodb_buffer_pool_size/innodb_buffer_pool_instances的值

Buffer Pool 总结
1)、 磁盘太慢,用内存作为缓存很有必要。
2)、 Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。
3)、 BufferPool向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后,Buffer Pool剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为 碎片 
4)、 InnoDB使用了许多 链表 来管理Buffer Pool
5)、 free 链表 中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到Buffer Pool时,会从free 链表 中寻找空闲的缓存页
6)、 为了快速定位某个页是否被加载到Buffer Pool,使用 表空间号 +  页号 作为key,缓存页作为value,建立哈希表
7)、 在Buffer Pool中被修改的页称为 脏页 ,脏页并不是立即刷新,而是被加入到flush 链表 中,待之后的某个时刻同步到磁盘上
8)、 LRU 链表 分为young和old两个区域,可以通过innodb_old_blocks_pct来调节old区域所占的比例。首次从磁盘上加载到Buffer Pool的页会被放到old区域的头部,在innodb_old_blocks_time间隔时间内访问该页不会把它移动到young区域头部。在BufferPool没有可用的空闲缓存页时,会首先淘汰掉old区域的一些页
9)、 我们可以通过指定innodb_buffer_pool_instances来控制Buffer Pool实例的个数,每个Buffer Pool实例中都有各自独立的链表,互不干扰。
10)、自MySQL5.7.5版本之后,可以在服务器运行过程中调整BufferPool大小。每个BufferPool实例由若干个chunk组成,每个chunk的大小可以在服务器启动时通过启动参数调整。
11)、可以用下边的命令查看Buffer Pool的状态信息:SHOW ENGINE INNODB STATUS

explain 执行计划

查看某个执行计划花费的成本的方式:在EXPLAIN单词和真正的查询语句中间加上 FORMAT=JSON,只需关注 prefix_cost 变量值就是查询 s1 表的成本
例如:EXPLAIN FORMAT=JSON SELECT * FROM s1 

查看与这个查询的执行计划有关的一些扩展信息:使用EXPLAIN语句查看了某个查询的执行计划后,再使用 SHOW WARNINGS 语句,当Code值为1003时,Message字段展示的信息类似于查询优化器将我们的查询语句重写后的语句
查看优化器生成执行计划的过程: optimizer trace, SET optimizer_trace="enabled=on"

可以通过查看 information_schema 数据库下的 OPTIMIZER_TRACE 表如下四个字段:

QUERY:表示我们的查询语句

TRACE:表示优化过程的 JSON 格式文本

MISSING_BYTES_BEYOND_MAX_MEM_SIZE:由于优化过程可能会输出很多,如果超过某个限制时,多余的文本将不会被显示,这个字段展示了被忽略的文本字节数

INSUFFICIENT_PRIVILEGES:表示是否没有权限查看优化过程,默认值是 0 ,只有某些特殊情况下才会是1,我们暂时不关心这个字段的值

优化过程总体上分为三个阶段:prepare阶段、optimize阶段、execute阶段,成本的优化主要集中在 

单表查询来说,我们主要关注 optimize阶段的 rows_estimation 这个过程,这个过程深入分析了对单表查询的各种执行方案的成本;
对于多表连接查询来说,我们更多需要关注 considered_execution_plans  这个过程

mysql子查询优化

由于相关子查询并不是一个独立的查询,所以不能转换为物化表来执行查询

semi-join(半连接) 的适用条件:

1)、该子查询必须是和IN语句组成的布尔表达式,并且在外层查询的WHERE或者ON子句中出现。
2)、外层查询也可以有其他的搜索条件,只不过和IN子查询的搜索条件必须使用AND连接起来。
3)、该子查询必须是一个单一的查询,不能是由若干查询由UNION连接起来的形式。
4)、该子查询不能包含GROUP BY或者HAVING语句或者聚集函数

不适用于 semi-join 的条件:

1)、外层查询的 WHERE 条件中有其他搜索条件与 IN 子查询组成的布尔表达式使用OR连接起来
2)、使用NOT IN而不是IN的情况
3)、在SELECT子句中的 IN 子查询的情况
4)、子查询中包含GROUP BY、HAVING或者聚集函数的情况
5)、子查询中包含UNION的情况
6)、对于不相关子查询来说,可以尝试把它们物化之后再参与查询(比如 not in 不相关子查询)
即先将子查询物化,然后再判断key1是否在物化表的结果集中可以加快查询执行的速度。请注意这里将子查询物化之后不能转为和外层查询的表的连接,只能是先扫描 s1 表,然后对 s1 表的某条记录来说,判断该记录的 key1 值在不在物化表中
7)、不管子查询是相关的还是不相关的,都可以把IN子查询尝试专为EXISTS子查询

子查询优化总结:

如果IN子查询符合转换为semi-join的条件,查询优化器会优先把该子查询转换为semi-join,然后再考虑下边五种执行半连接的策略中哪个成本最低:

1)、Table pullout  (子查询中的表上拉):当子查询的查询列表处只有主键或者唯一索引列时,可以直接把子查询中的表上拉 到外层查询的FROM子句中

2)、DuplicateWeedout execution strategy  (重复值消除):使用临时表消除semi-join结果集中的重复值的方式称之
为DuplicateWeedout

3)、LooseScan execution strategy  (松散索引扫描):扫描索引,但只取值相同的记录的第一条去做匹配操作的方式称之为松散索引扫描

4)、Semi-join Materialization execution strategy:先将外层查询的IN子句中的不相关子查询进行物化,然后再进行外层查询的表和物化表的连接本质上也算是一种semi-join,只不过由于物化表中没有重复的记录,所以可以直接将子查询转为连接查询

5)、FirstMatch execution strategy  (首次匹配):先取一条外层查询的中的记录,然后到子查询的表中寻找符合匹配条件的记录,如果能找到一条,则将该外层查询的记录放入最终的结果集并且停止查找更多匹配的记录,如果找不到则把该外层查询的记录丢弃掉;然后再开始取下一条外层查询中的记录,重复上边这个过程

如果IN子查询不符合转换为semi-join的条件,那么查询优化器会从下边两种策略中找出一种成本更低的方式执行子查询:

1)、先将子查询物化之后再执行查询
2)、执行IN to EXISTS转换

MySQL会在包含GROUP BY子句的查询中默认添加上ORDER BY子句,也就是说上述查询其实和下边这个查询等价:
EXPLAIN SELECT common_field, COUNT(*) AS amount FROM s1 GROUP BY common_field ORDER BY common_field;
如果我们并不想为包含GROUP BY子句的查询进行排序,需要我们显式的写上 ORDER BY NULL

NULL 值是否使用索引

他们提供了一个名为 innodb_stats_method 的系统变量,相当于在计算某个索引列不重复值的数量时如何对待NULL值这个锅甩给了用户,这
个系统变量有三个候选值:
1)、nulls_equal:认为所有NULL值都是相等的。这个值也是 innodb_stats_method 的默认值
如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别多,所以倾向于不使用索引进行访问。

2)、nulls_unequal:认为所有NULL值都是不相等的
如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别少,所以倾向于使用索引进行访问。

3)、nulls_ignored:直接把NULL值忽略掉
反正这个锅是甩给用户了,当你选定了innodb_stats_method值之后,优化器即使选择了不是最优的执行计划,那也跟设计MySQL的大叔们没关系了哈~当然对于用户的我们来说,最好不在索引列中存放 NULL 值才是正解

InnoDB 统计数据

1)、InnoDB以表为单位来收集统计数据,这些统计数据可以是基于磁盘的永久性统计数据,也可以是基于内存的非永久性统计数据

2)、innodb_stats_persistent控制着使用永久性统计数据还是非永久性统计数据
innodb_stats_persistent_sample_pages控制着永久性统计数据的采样页面数量
innodb_stats_transient_sample_pages控制着非永久性统计数据的采样页面数量
innodb_stats_auto_recalc控制着是否自动重新计算统计数据

3)、我们可以针对某个具体的表,在创建和修改表时通过指定STATS_PERSISTENT、STATS_AUTO_RECALC、STATS_SAMPLE_PAGES的值来控制相关统计数据属性

4)、innodb_stats_method决定着在统计某个索引列不重复值的数量时如何对待NULL值

mysql数据迁移

1)、无分库分表的数据迁移,直接把主库的数据进行复制,直接操作源文件,然后再 dump 时增加固定参数 --single-transaction,同时master 和 slave 必须要开启 binlog 的主从复制
2)、分库分表的数据迁移,需要将数据根据指定的分片规则切分,此时没有办法拷贝数据文件,只能使用工具或者代码进行数据的迁移

表数据迁移时先关闭索引,导入数据后再开启索引创建

外连接和内连接的本质区别

对于外连接的驱动表的记录来说,如果无法在被驱动表中找到匹配 ON 子句中的过滤条件的记录,那么该记录仍然会被加入到结果集中,对应的被驱动表记录的各个字段使用 NULL 值填充;而内连接的驱动表的记录如果无法在被驱动表中找到匹配 ON 子句中的过滤条件的记录,那么该记录会被舍弃

凡是不符合 WHERE 子句中条件的记录都不会参与连接。只要我们在搜索条件中指定关于被驱动表相关列的值不为NULL,那么外连接中在被驱动表中找不到符合ON子
句条件的驱动表记录也就被排除出最后的结果集了,也就是说:在这种情况下:外连接和内连接也就没有什么区别了!

我们把这种在外连接查询中,指定的WHERE子句中包含被驱动表中的列不为NULL值的条件称之为 空值拒绝 (英文名:reject-NULL)。
在被驱动表的 WHERE 子句符合空值拒绝的条件后,外连接和内连接可以相互转换。这种转换带来的好处就是查询优化器可以通过评估表的不同连接顺序的成本,选出成本最低的那种连接顺序来执行查询

​​​​​​​

select * from t where a = 1 查询时,IO次数,考虑最多,单表3000万数据时

1)、覆盖索引的话,最多3次,也可以说最多2次,看是否考虑mysql启动那次

2)、主键索引,最多3次

3)、辅助索引,最多6次

4)、之前查询过了的话,0次

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/104949037
今日推荐