面试常问
如何定位慢语句
如果业务中出现查询需要很长时间才返回的情况,可以使用慢查询日志进行慢语句查询
MySQL 的慢查询日志记录的内容是:在 MySQL 中响应时间超过参数 long_query_time(单位秒,默认值 10)设置的值并且扫描记录数不小于 min_examined_row_limit(默认值0)的语句。
默认情况下,慢查询日志中不会记录管理语句,如果需要记录的请做如下设置,设置log_slow_admin_statements = on 让管理语句中的慢查询也会记录到慢查询日志中。默认情况下,也不会记录查询时间不超过 long_query_time 但是不使用索引的语句,可通过配置log_queries_not_using_indexes = on 让不使用索引的 SQL 都被记录到慢查询日志中(即使查询时间没超过 long_query_time 配置的值)。
一般使用慢查询的步骤如下:
- 开启慢查询日志
mysql> set global slow_query_log = on;
Query OK, 0 rows affected (0.00 sec)
- 设置时间限制
mysql> set global long_query_time = 1;
Query OK, 0 rows affected (0.00 sec)
- 查看慢日志路径
mysql> show global variables like "datadir";
- 查看日志文件名
mysql> show global variables like "slow_query_log_file";
- 查看慢语句
SELECT * FROM mysql.slow_log
或者打开日志文件查看
有时慢查询正在执行,已经导致数据库负载偏高了,而由于慢查询还没执行完,因此慢查询日志还看不到任何语句。此时可以使用 show processlist 命令判断正在执行的慢查询。show processlist 显示哪些线程正在运行
善用explain
explian可以分析某个语句的状态,使用很简单,直接在查询语句前加上explain就可以,例如
mysql> explain select * from servers;
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | servers | ALL | NULL | NULL | NULL | NULL | 1 | NULL |
+----+-------------+---------+------+---------------+------+---------+------+------+-------+
1 row in set (0.03 sec)
expain出来的信息有10列,分别是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra,下面对这些字段出现的可能进行解释:
列名 | 描述 |
id | 在一个大的查询语句中,每个SELECT关键字都对应一个唯一的id |
select_type | SELECT关键字对应的查询的类型 |
table | 表名 |
partitions | 匹配的分区信息 |
type | 针对单表的访问方法 |
possible_keys | 可能用到的索引 |
key | 实际使用的索引 |
key_len | 实际使用的索引长度 |
ref | 当使用索引列等值查询时,与索引列进行等值匹配的对象信息 |
rows | 预估的需要读取的记录条数 |
filtered | 针对预估的需要读取的记录,经过搜索条件过滤后剩余记录条数的百分比 |
Extra | 一些额外的信息 |
一般如果建立了索引,我们会看type字段,这个字段有如下:
- system:一般整个数据库只有一条记录,那么就是system,能精确的查找
- const:当我们使用id或者唯一二级索引进行查找的时候,对单表的访问就是const
- eq_ref:执行连接查询时,如果被驱动表通过id或者唯一二级索引进行查找的就是eq_ref。说到被驱动表,当连接查询没有where条件时,左连接查询时,前面的表是驱动表,后面的表是被驱动表,右连接查询时相反,内连接查询时,哪张表的数据较少,哪张表就是驱动表 当连接查询有where条件时,带where条件的表是驱动表,否则是被驱动表
- ref:通过普通的二级索引进行查找就是ref
- fulltext:全文索引
- ref_or_null:通过普通的二级索引进行查找,并且该索引列的值可以是NULL时
- index_merge:在一些索引合并的场景下
- unique_subquery:类似于eq_ref,优化器对IN查询优化为EXISTS子查询时,并且转换后通过id或者唯一二级索引进行查找
- index_subquery:类似于unique_subquery,只不过在子查询使用的普通索引
- range:通过索引获取扫描区间的记录
- index:使用覆盖索引的时候
- ALL:全表扫描
以上介绍的顺序是由快到慢的
如何更好的建立和使用索引
- 只为用于搜索、排序和分组的列创建索引
- 索引列中重复的值不能太多
- 索引的类型尽量小,能用INT就不要BIGINT,能INT就不要VCHAR
- 字符太长的时候只为前缀建立索引
- 尽量使用覆盖索引
- 多个索引间注意不要出现有包含关系的索引,避免冗余
查询语句如何优化
- 尽量避免在 where 子句中对字段进行 null 值判断 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。
- 尽量避免在 where 子句中使用 or 来连接条件 尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。
- in 和 not in 要慎用 尽量使用between,否则导致全表扫描
- 如果在 where 子句中使用参数,也会导致全表扫描 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
- 尽量避免在where子句中对字段进行函数操作 应尽量避免在where子句中进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
- exists 代替 in 使用in,优化器会优化为exists
- 尽量不要修改聚簇索引的列,会导致整个表改动,例如主键。
- 尽量使用int代替字符串
分表分库
有时候查询慢不一定是写的有问题,随着业务拓展,数据越来越多,查询效率也会随之降低,这时候可能就需要进行分表分库操作来缓解
何时分表
当查询慢的主要原因是数据量太大时,一般采用分表
如何分表
- 水平分表 单表的数据量太大。按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。这种情况是不建议使用的,因为数据量是逐渐增加的,当数据量增加到一定的程度还需要再进行切分。比较麻烦。
- 垂直分表 表中的字段较多,一般将不常用的、 数据较大、长度较长的拆分到“扩展表“。一般情况加表的字段可能有几百列,此时是按照字段进行数竖直切。注意垂直分是列多的情况。
何时分库
如果单库达到了性能瓶颈,同时并发量还挺高的时候,考虑采用分库的方法
如何分库
- 水平分库 水平分库理论上切分起来是比较麻烦的,它是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。 水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
- 垂直分库 一个数据库的表太多。此时就会按照一定业务逻辑进行垂直切,比如用户相关的表放在一个数据库里,订单相关的表放在一个数据库里。注意此时不同的数据库应该存放在不同的服务器上,此时磁盘空间、内存、TPS等等都会得到解决。
缓存和数据库
优化的地方都离不开局部性原理,对于经常访问的热点数据,可以采用缓存的方式存储,先访问缓存,如果缓存没有命中,再到数据库里查找。
分布式数据库
上面说到的分库也算是分布式下的一种情形,不过每个节点保存的不同数据,用于分担单机压力。这里说的主要是主从结构的分布式,为了实现高可用和缓解高并发的情况
数据库主从结构可以是一对多,一对一,多对一
主从复制原理和过程
MySQL 主从复制涉及到三个线程:
一个在主节点的线程:log_dump_thread
从库会生成两个线程:一个 I/O 线程,一个 SQL 线程
主库会生成一个 log dump 线程,用来给从库 I/O 线程传 Binlog 数据。
从库的 I/O 线程会去请求主库的 Binlog,并将得到的 Binlog 写到本地的 relay log (中继日志)文件中。
SQL 线程,会读取 relay log 文件中的日志,并解析成 SQL 语句逐一执行。
事务的提交
分布式事务一般有两段式提交和三段式提交,这两种提交方式可以参考这里
对于两段式提交的过程如下:
- Master执行提交语句时,事务被发送到slave,slave开始准备事务的提交。
- 每个slave都要准备事务,然后向master发送OK(或ABORT)消息,表明事务已经准备好(或者无法准备该事务)。
- Master等待所有Slave发送OK或ABORT消息,如果Master收到所有 Slave的OK消息,它就会向所有Slave发送提交消息,告诉Slave提交该事务;如果Master收到来自任何一个Slave的ABORT消息,它就向所有 Slave发送ABORT消息,告诉Slave去中止事务。
- 每个Slave等待来自Master的OK或ABORT消息。 如果Slave收到提交请求,它们就会提交事务,并向Master发送事务已提交 的确认;如果Slave收到取消请求,它们就会撤销所有改变并释放所占有的资源,从而中止事务,然后向Masterv送事务已中止的确认。
- 当Master收到来自所有Slave的确认后,就会报告该事务被提交(或中止),然后继续进行下一个事务处理。