鸟瞰 MySQL,唬住面试官!
The following article is from 江南一点雨 Author muggle
1. MySQL
1.1 mysql 架构
mysql 分为 server 层和存储引擎
1.1.1 server层
连接器:管理连接权限验证
查询缓存:命中缓存直接换回查询结果
分析器:分析语法
优化器:生成执行计划,选择索引
执行器:操作索引返回结果
1.1.2 存储引擎
存储引擎负责数据的存储和提取;其架构是插件式的。innodb 在 mysql5.5.5 版本开始成为 mysql 默认存储引擎。
各存储引擎比对:
InnoDB:支持事务,支持外键,InnoDB 是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据,不支持全文索引。
MyISAM:不支持事物,不支持外键,MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的,查询效率上 MyISAM 要高于 InnnDB ,因此做读写分离的时候一般选择用 InnoDB 做主机,MyISAM 做从机
Memory:有比较大的缺陷使用场景很少;文件数据都存储在内存中,如果 mysqld 进程发生异常,重启或关闭机器这些数据都会消失。
1.1.3 sql 的执行过程
第一步客户端连接上 mysql 数据库的连接器,连接器获取权限,维持管理连接;连接完成后如果你没有后续的指令这个连接就会处于空闲状态,如果太长时间不使用这个连接这个连接就会断开,这个空闲时长默认是 8 小时,由 wait_timeout 参数控制。
第二步你往 mysql 数据库发送了一条 sql ,这个时候查询缓存开始工作,看看之前有没有执行过这个 sql ,如果有则直接返回缓存数据到客户端,只要对表执行过更新操作缓存都会失效,因此一些很少更新的数据表可考虑使用数据库缓存,对频繁更新的表使用缓存反而弊大于利。使用缓存的方法如以下 sql ,通过 SQL_CACHE 来指定:
select SQL_CACHE * from table where xxx=xxx
1.2 mysql 日志
1.2.1 mysql 日志介绍
1.2.2 数据更新过程
1.3 mysql 的 mvcc
1.3.1 快照读和当前读
select
语句,形如:select * from table
Repeatableread
事务隔离级别下,快照读的特点是获取当前数据库的快照数据,对于所有未 commit 的数据都不可见,快照读不会对数据上锁。select
...
lock in share mode
select
...
for
update
insert
update
delete
1.3.2 mvcc 原理
InnoDB 只查找创建版本早于当前事务版本的数据行,即,行的系统版本号小于或等于事务的系统版本号,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。 行的删除版本,要么未定义,要么大于当前事务版本号。这样可以确保事务读取到的行,在事务开始之前未被删除。只有符合上述两个条件的纪录,才能作为查询结果返回。
1.4 mysql 索引
1.4.1 索引介绍
哈希表适用于只有等值查询的场景 有序数组适用于有等值查询和范围查询的场景,但有序数组索引的更新代价很大,所以最好用于静态数据表 搜索树的搜索效率稳定,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高 跳表可以理解为优化的哈希索引
select * from table where key = 1 ;
select id from table where key = 1 ;
1.4.2 普通索引和唯一索引
change buffer
中,在下次查询需要访问这个数据页的时候再写入更新操作除了查询会将 change buffer
写入磁盘,后台线程线程也会定期将 change buffer
写入到磁盘中。对于唯一索引来说所有的更新操作都要先判断这个操作是否会违反唯一性约束,因此唯一索引的更新无法使用 change buffer
而普通索引可以,唯一索引更新比普通索引更新多一个唯一性校验的过程。1.4.3 联合索引
selet * from table where a = xxx and b = xxx
a=xxx
数据 再从 a=xxx
中查出 b=xxx
的数据。使用索引下推和不使用索引下推的区别在于不使用索引下推会先查出 a=xxx
数据的主键然后根据查询出的主键回表查询出全行数据,再在全行数据上查出 b=xxx
的数据;而索引下推的执行过程是先查出 a=xxx
数据的主键,然后在这些主键上二次查询 b=xxx
的主键,然后回表。innodb 引擎的表,索引下推只能用于二级索引 索引下推一般可用于所查询字段不全是联合索引的字段,查询条件为多条件查询且查询条件子句字段全是联合索引。
1.4.4 优化器与索引
1.4.5 其他索引知识点
1.5 mysql 语法分析及优化
1.5.1 count(*)
count(*)
对 innodb 而言,它需要把数据从磁盘中读取出来然后累计计数;而 MyISAM 引擎把一个表的总行数存在了磁盘上,所以执行 count(*)
会直接返回这个数,如果有 where 条件则和 innodb一样。那么如何优化 count(*)
?一个思路是使用缓存,但是需要注意双写一致的问题(双写一致性后文缓存章节会做介绍)。还可以专门设计一张表用以存储 count(*)
。count(*)=count(1)>count(id)>count(字段)
1.5.2 order by
sort_buffer
,一个包含排序的 sql 执行过程为:申请排序内存 sort_buffer
,然后一条条查询出整行数据,然后将需要的字段数据放入到排序内存中,染回对排序内存中的数据做一个快速排序,然后返回到客户端。当数据量过大,排序内存盛不下的时候就会利用磁盘临时文件来辅助排序。当我们排序内存盛不下数据的时候,mysql 会使用 rowid
排序来优化。rowid 排序相对于全字段排序,不会把所有字段都放入 sort_buffer,所以在 sort buffer 中进行排序之后还得回表查询。在少数情况下,可以使用联合索引+索引覆盖的方式来优化 order by。1.5.3 join
join
之前我们应该先了解驱动表这个概念——当两表发生关联的时候就会有驱动表和被驱动表之分,驱动表也叫外表(R 表),被驱动表也叫做内表(S 表)。一般我们将小表当做驱动表(指定了联接条件时,满足查询条件的记录行数少的表为「驱动表」,未指定联接条件时,行数少的表为「驱动表」;MySQL 内部优化器也是这么做的)。select * from table1 left join tablet2 on table1.xxx=table2.xxx
NLJ
。当 xxx 不是索引的时候,再使用 NLJ
的话就会对 table2 做多次的全表扫描(每从 table1 取一条数据就全表扫描一次 table2),扫描数暴涨。这个时候 mysql 会采用另外一个查询策略。Mysql 会先把 table1 的数据读入到一个 join_buffer
的内存空间里面去,然后依次取出 table2 的每一行数据,跟 join_buffer
中的数据做对比,满足 join 条件的作为结果集的一部分返回。join
的时候,要遵循以下几点:小表驱动大表。 被驱动表走索引的情况下(走 NLJ
查询方式)的时候才考虑用join
1.5.4 sql 的优化
select * from tradelog where month ( data )= 1 ;
select * from table where xxx = 110717 ;
varchar
型,在 mysql 中,字符串和数字做比较的话,将字符串转换成数字再进行比较,这里相当于使用了 CAST(xxx ASsigned)
导致无法走索引。1.6执行计划和慢查询日志
1.6.1 执行计划
explain
可查看该条 sql 的执行计划,如:EXPLAIN SELECT * FROM table
SIMPLE:查询中不包含子查询或者UNION; PRIMARY:查询中包含复杂的子部分; SUBQUERY:在SELECT或WHERE列表中包含了子查询,该子查询被标记为SUBQUERY; DERIVED:衍生,在FROM列表中包含的子查询被标记为DERIVED; UNION:若第二个SELECT出现在UNION之后,则被标记为UNION; UNION RESULT:从UNION表获取结果的SELECT被标记为UNION RESULT;
ALL:Full Table Scan, MySQL 将遍历全表以找到匹配的行; index:Full Index Scan,index 与 ALL 区别为 index 类型只遍历索引树; range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于 between < > 等查询; ref:非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找; eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描; onst 和 system:当 MySQL 对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量,system 是 const 类型的特例,当查询的表只有一行的情况下, 使用 system; NULL:MySQL 在优化过程中分解语句,执行时甚至不用访问表或索引。
Using index:该值表示相应的 select 操作中使用了覆盖索引; Using where:MySQL 将用 where 子句来过滤结果集; Using temporary:表示 MySQL 需要使用临时表来存储结果集,常见于排序和分组查询; Using filesort:MySQL 中无法利用索引完成的排序操作称为“文件排序”。
1.6.2 慢查询日志
long_query_time
指定, long_query_time
的默认值为 10,运行 10S 以上的查询 sql 会被记录到慢查询日志中。默认情况下,Mysql 数据库并不启动慢查询日志,需要我们手动来设置这个参数。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。show variables like '%slow_query_log%' ;
set global slow_query_log = 1 ;
show variables like 'long_query_time%' ;
set global long_query_time = 4 ;
1.7 主从备份
1.7.1 主从备份原理
logdump
线程,用于发送 binlog
的内容。在读取 binlog
中的操作时,此线程会对主节点上的 binlog
加锁,当读取完成,甚至在发动给从节点之前,锁会被释放。binlog
复制到本地的 relay log
中,首先,从库库会先启动一个工作线程,称为IO工作线程,负责和主库建立一个普通的客户端连接。如果该进程追赶上了主库,它将进入睡眠状态,直到主库有新的事件产生通知它,他才会被唤醒,将接收到的事件记录到 relay log
(中继日志)中。relay log
中的内容,解析成具体的操作并执行,最终保证主从数据的一致性。1.7.2 主从备份延迟
relay log
)的速度,比主库生产 binlog
的速度要慢。可能导致的原因有:大事务,主库上必须等事务执行完成才会写入 binlog,再传给备库,当一个事物用时很久的时候,在从库上会因为这个事物的执行产生延迟。 从库压力大。
一主多从——多接几个从库,让这些从库来分担读的压力。这样方法适用于从库读压力大的时候。 通过 binlog 输出到外部系统,比如 Hadoop 这类系统,让外部系统提供统计类查询的能力
1.8 分布式事务
1.8.1 XA 分布式事务
资源管理器(resource manager):用来管理系统资源,是通向事务资源的途径。数据库就是一种资源管理器。资源管理还应该具有管理事务提交或回滚的能力。 事务管理器(transaction manager):事务管理器是分布式事务的核心管理者。事务管理器与每个资源管理器(resource manager)进行通信,协调并完成事务的处理。事务的各个分支由唯一命名进行标识。
-- 开启一个事务,并将事务置于 ACTIVE 状态,此后执行的 SQL 语句都将置于该是事务中。
XA START xid
-- 将事务置于 IDLE 状态,表示事务内的 SQL 操作完成。
XA END xid
-- 事务提交的准备动作,事务状态置于 PREPARED 状态。事务如果无法完成提交前的准备操作,该语句会执行失败。
XA PREPARE xid
-- 事务最终提交,完成持久化。
XA COMMIT xid
-- 事务回滚终止
XA ROLLBACK xid
-- 查看 MySQL中存在的 PREPARED 状态的 xa 事务。
XA RECOVER
PREPARE
状态,普通事务是 begin-> commit 而分布式事务是 begin->PREPARE 等其他数据库事务都到 PREPARE 状态的时候再 PREPARE->commit。分布式事务 sql 示例:xa start 'aaa' ;
insert into table ( xxx ) values ( xxx );
xa end 'aaa' ;
xa prepare 'aaa';
xa commit 'aaa';
单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞状态,直到提交完成才能释放资源。 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务 commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
1.8.2 TCC 分布式事务
update stock set lock = 1 where id = 1 ;
insert into credits (lock,...)values (1,...)
Seata
ByteTCC
tcc-transaction
等。