MySQL_初探索引优化_查询优化

文章目录

      • MySQL的配置文件
      • MyISAM和InnoDB的区别
      • SQL执行顺序
      • 索引优化
        • 什么是索引
        • 执行计划
        • 索引优化
      • 查询优化
          • **小表驱动大表**
          • SQL排序
        • 慢查询日志
        • mysqldumpslow日志分析工具
        • Show Profile
        • 全局查询日志
      • MySQL锁机制
        • 锁的分类
          • 表锁
          • 行锁
      • 主从复制
        • 主机修改配置文件my.ini
        • 在主机上建立账户并授权给从机

MySQL的配置文件

  • 二进制日志
    log-bin用于记录数据库的写行为,用于主从复制
  • 错误日志
    log-error默认关闭,用于记录严重的错误和警告信息,每次启动和关闭的详细信息
  • 查询日志
    log默认关闭,记录查询的SQL语句,开启会降低MySQL的整体性能
  • 数据文件
    • frm文件:存放表结构
    • myd文件:存放表数据
    • myi文件:存放表索引
  • 配置文件
    windows:my.ini
    Linux:my.cnf

MyISAM和InnoDB的区别

  • MyISAM不支持事务处理、外键等高级数据库功能,而InnoDB支持
  • MyISAM强调的是性能,其执行速度比InnoDB更快
  • MyISAM只有表锁,InnoDB支持行锁(基于索引的行锁)
  • InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要检索出这个变量就行

综上,MyISAM引擎适合读多写少的情况,而大量的写操作则需要用InnoDB来保证性能和数据的准确性。而InnoDB也是MySQL5.5之后的默认引擎

SQL执行顺序

SELECT DISTINCT
FROM tbleName1
[INNER|LEFT|RIGHT] JOIN tableName2
ON
    [连接条件]
WHERE
    [筛选条件]
GROUP BY
    [分组条件]
HAVING
    [分组后筛选条件]
ORDER BY [排序字段] [ASC|DESC]
LIMIT
    [起始索引数,每页条目数]
    [size*(page-1),size]

MySQL执行SQL的顺序从From开始,以下是执行的顺序流程

FROM->WHERE->GROUP BY->HAVING->SELECT->DISTINCT->ORDER BY->LIMIT

  1. FROM tbleName1[INNER|LEFT|RIGHT] JOIN tableName2将两个表中的数据产生笛卡尔积,生成临时表V1。再将ON中连接条件应用到V1的每一行,再生成一个临时表V2。这时再判断是LEFT JOIN还是RIGHT JOIN,然后把刚才相应的表中被过滤的行添加到V2中生成临时表V3。如果FROM中关联了两个表以上则第三表和V3重复上面的过程
  2. WHERE对临时表V3进行过滤产生中间表V4。应用where筛选器,对上一步生产的虚拟表引用WHERE筛选器,生成虚拟表V4,ON和WHERE最大区别在于,如果在ON应用逻辑表达式那么在第三步OUTER JOIN中还可以把移除的行再次添加回来,而WHERE的移除是不可恢复的
  3. GROUP BY 对中间表V4进行分组,产生虚拟表V5。GROUP BY子句将中的唯一的值组合成为一组,得到虚拟表V5。如果使用了GROUP BY,那么后面的所有步骤都只能得到的V5的列或者是聚合函数(COUNT、SUM、MAX等)。原因在于最终的结果集中只为每个组包含一行
  4. HAVING对分组后的记录进行筛选,和WHERE的作用是一样的产生虚拟表V6
  5. SELECT对虚拟表V6进行列筛选,选择出要表示的列,产生虚拟表V7
  6. DISTINCT对中间表V7进行去重,产生中间表V8
  7. ORDER BY对V8中的数据进行排序,产生中间表V9。排序是很需要成本的,除非你必须要排序,否则最好不要指定排序
  8. LIMIT对中间表V9进行分页,产生最终结果

所有的查询语句都是从from开始执行的,在执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。

索引优化

SQL慢是指执行时间长或等待时间长,原因是

  • 查询语句写的有问题
  • 索引失效
  • 关联查询太多
  • 服务器参数配置不合理或者已达到性能瓶颈

什么是索引

排好序的、用于快速查找的数据结构。在数据之外,数据库还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。对于我们Java程序员来说,这种索引就是B TREE索引(多路搜索树)
索引的优点

  • 提高查询的速度,提高数据库的效率,降低数据库的IO成本
  • 通过索引对数据库进行排序,降低了排序和分组的成本,减少了CPU的消耗

索引的缺点

  • 索引的本质也是一张表,保存了主键和索引字段,并指向实体表的字段。索引是用空间来换取时间,缩短查询的时间成本的同时,要牺牲磁盘空间成本来存储索引
  • 索引会提高查询速度,但是会降低表的更新速度。索引创建之后,在对数据库表中的数据进行增、删、改等操作之后,相应的索引也需要进行维护

索引的种类

  • 单值索引
    一个索引只包含一个列,一个表可以有多个单值索引
  • 复合索引
    一个索引包含多个列
  • 唯一索引
    索引列的值唯一,但是允许有空值
#创建索引
#对于创建索引时如果是blob和text类型,必须指定length
CREATE [UNIQUE] INDEX indexName ON tableName(columnName1,columnName2)
ALTER TABLE tableName ADD [UNIQUE] INDEX [indexName] ON (columnName)
#删除索引
DROP INDEX indexName ON tableName
#查看索引
SHOW INDEX FROM tableName

左连接建右表
多表建立连接的时候,左连接将索引建在右表的主外键上,右连接将索引建立在左表的主外键上。因为左连接的时候左表的数据全部都有,而右表的数据需要筛选,也就是说主表是一定需要全表扫描的,而子表不需要,所以索引建在子表上

  • 哪些情况要创建索引

    • 主键自动创建唯一索引
    • 频繁作为查询条件的字段应该创建索引
    • 查询中与其它表建立联系的字段,外键关系建立索引
    • 高并发的情况下,偏向复合索引
    • 查询中排序的索引,排序字段若通过索引去访问将大大的提高排序速度
    • 查询中统计或分组的字段要创建索引
  • 哪些情况不要创建索引

    • 表中的记录太少
    • 经常增删改的表
    • 数据重复且平均分布的表字段
    • WHERE条件里面用不到的字段不创建索引
    • 频繁更新的字段不适合创建索引,因为每次更新还需要维护索引,加重数据库的IO负担

约定一般来说一张表不要建立超过5个索引

执行计划

使用EXPLAIN来模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析查询语句的性能瓶颈

#使用方法 EXPLAIN + 目标SQL
EXPLAIN SELECT * FROM human
  • id
    SELECT的查询序号,表示查询中执行的SELECT子句或者操作表的顺序。id如果相同的话,自上而下执行;id不同的话,id越大越优先执行

  • select type

    • simple
      简单查询,不包含子查询或者关联查询
    • primary
      查询中若包含子查询,主查询被标记为primary
    • subquery
      SELECT或WHERE中包含了子查询,子查询会被标记为subquery
    • derived
      在from列表中包含的子查询被标记为derived(衍生),即查的表是其他查询生成的中间表,后面会有一个数字,那是id,可以看出是那一个表的衍生表
    • union
      如果第二个select出现在union后面,则被标记为union,如果union包含在from子句的子查询中,外层的select被标记为derived
    • union select
      从union的结果中获取结果的select
  • table
    显示这一行数据是关于那一个表的

  • type
    重要参数,主要根据这个来判断一条SQL的好坏

    • system
      表中只有一行数据,基本上是系统变量
    • const
      表只有一行匹配,并在查询开始时就被读取,因此被优化器视为常数。当主键或者唯一索引和一个常数比较时会出现
    • eq_ref
      每次匹配时只会读取一行,当主键或者唯一非空索引(unique not null index)的全部或部分被使用时会出现
    • ref
      每次匹配时会读取所有匹配行,当用到了非唯一索引或者唯一索引的一部分时会出现,等值匹配
    • range
      根据索引选取一定范围内的行,范围匹配
    • index
      从索引中获取所有的数据,不需要访问原表。(对于innodb引擎,索引中会有主键数据)或者根据索引的顺序进行全表扫描
    • all
      全表扫描
      [system>>eq_ref>ref>range>index>all]起码达到range级别,最好能达到ref
  • possible_keys
    可能用到的索引,一个或者多个,查询涉及到的字段只要存在索引就会被列出,但不一定会被使用

  • key
    实际使用的索引,不一定在possible_keys中存在,如果是null则没有使用索引

  • key_len
    表示使用的索引的长度,可以用来判断多列索引中的哪些部分被使用到了。在不损失精确性的情况下,越短越好

  • ref
    匹配时使用到的列或者常数,如果值是func,表示匹配时用到了由函数生成的值(±、*/也算)。如果要看使用到了哪些函数,可以在explain语句执行后使用show warnings

  • rows
    检查的行数,Innodb引擎是个估计值

  • Extra
    包含不适合在其它列中显示但十分重要的信息

    • Using filesort
      说明MySQL会对数据使用一个外部的索引排序,而不是按照表内索引的顺序进行读取。也就是说MySQL的排序没有用到索引,这种情况尽量进行优化
    • Using temporary
      使用了临时表来保存结果,常见于分组和排序,这种情况非常影响性能,是一定需要进行优化的
    • Using index
      表示使用了覆盖索引,避免访问了表的数据行。如果同时出现Using where,表明索引被用来执行索引键值的查找,如果没有则表明索引用来读取数据而非执行查找动作
    • Using where
      表明使用了where过滤
    • Using join buffer
      表名使用了连接缓存,如果多表关联join特别多的话需要把配置文件中的join buffer调大一点
    • impossible where
      where子句的值总是false,获取不到任何行
    • select tables optimiezd away
      在没有order by的情况下,基于索引优化min/max操作,或者对于MyISAM存储引擎优化count(*),不必等到执行阶段再计算,查询执行计划生成阶段即完成优化
    • distinct
      优化distinct操作,在找到第一匹配的元组之后立即停止找同样的值的操作

索引优化

  • 全值匹配
    即建立索引的字段,在查询语句中全部使用到,并且是等值,这是最理想的情况
  • 最佳左前缀法则
    有的时候我们用不到索引的所有列,但是我们必须要遵循最佳左前缀法则:查询从最左前列开始,并且不跳过中间列,但是如果是等值匹配的情况,abcd和abdc甚至dcba是一样的,自动查询优化器会自动调整
  • 不再索引字段上做任何操作
    函数,计算,自动或者手动的类型转换都会导致索引失效而转向全表扫描
  • 范围条件右边的列无效
    索引可以形象理解为,一级一级递增缩小的范围,比如说A区B栋3楼302房,这是一个索引,如果我只说A区B栋,那么找到B栋的时候,后面你不知道是具体几楼,所以说范围检索后面的列索引无效。所以我们会把范围检索放在最后面
  • 尽量使用覆盖索引
    只访问索引的查询(索引列和查询列一致),减少select*
  • 使用!=或者<>会导致索引失效变成全表扫描
  • 使用is null ,is not null会导致索引失效
  • 使用like关键字的时候前面通配符是%的时候(like(’%a’))MySQL索引失效
    所以like后面的百分号尽量不要放在左边。非要放左边的话可以使用覆盖索引来解决这个问题,但查询字段不可以超过建立索引的字段
  • 字符串不加单引号会导致索引失效
  • 使用or连接时会导致索引失效

查询优化

  1. 慢查询日志的开启并捕获慢SQL
  2. explain慢SQL分析
  3. show profile查询SQL在MySQL服务器里面的执行细节和生命周期情况
  4. SQL数据服务器的参数调优
小表驱动大表

我们在Java中写两个嵌套的循环,循环次数是两个循环的乘积,所以内层循环和外层循环的顺序没有关系。但是在MySQL中,每执行一次循环就需要建立一次连接,所以要求的是外层循环次数要比内层循环次数要小,也就是我们常说的小表驱动大表,大表指的是数据多的表,查询相当于是遍历这个表,数据越多遍历次数越多

SELECT * FROM A WHERE ID IN(SELECT ID FROM B);

这种情况会先去查询B表,再去查询A表,也就是说相当于先遍历B再遍历A,那么当B表的数据绝对小于A表的时候使用这种方式

SELECT * FROM A EXISTS( SELECT existsFlag FROM B WHERE A.ID=B.ID)

这种情况会先去查询A表,再去查询B,相当于先遍历A再去遍历B,当A表的数据绝对小于BB表的时候使用这种方式
exists关键字是将主查询的数据放在子查询中进行验证,根据验证结果来决定主查询的数据是否保留。exists只会返回一个boolean值所以select后面写什么没关系

SQL排序

MySQL支持两种排序方式FilesortIndex,Index的效率高,它是指MySQL扫描索引本身完成排序,Filesort的效率低ORDER BY尽量使用Index方式进行排序,避免使用FileSort的方式,如果不在索引列上,FileSort有两种算法,双路排序单路排序

  • 双路排序
    两次扫描,得到最终数据。从磁盘中读取满足查询条件的数据的行指针(记录目标行在表中的位置)和需要排序的列,对它们在sort_buffer中进行排序之后将排序结果丢给随机读取缓冲区(read_rnd_buffer),随机读取缓冲区根据指针信息再到磁盘中读取该行的其他字段数据
  • 单路排序
    一次扫描,得到最终数据。从磁盘中读取满足查询条件行的所有的列,按照Order By在buffer中对数据进行排序,然后扫描排序后的列表进行输出。单路排序的效率更高,避免了第二次读取数据,减少了IO次数,降低了磁盘的开销,但是会使用更多的空间,因为把所有的字段都保存在内存中了

但是因为单路排序需要更大的磁盘空间,如果sort_buffer_size的大小不足,可能会导致两次甚至更多次的IO次数
使用单路排序的条件

  • 查询语句所取出的字段类型大小总和要小于max_length_for_sort_data
  • 排序字段中不包含text和blob类型

当我们只能使用FileSort的时候我们可以通过增大sort_buffer和增大max_length_for_sort_data这两个属性来提高SQL效率max_length_for_sort_data是指查询列中除去textblob类型,其他字段大小的和,所以我们当使用了排序的时候,尽量不要使用SELECT *,只查询需要用到的字段
如何保证尽量使用Index排序

  • 排序字段和建立索引的字段一致
  • 排序字段遵循最佳左前缀原则
    使用WHERE语句和ORDER BY语句满足索引最左前列:如果WHERE中使用了索引字段作为等值条件,那么WHERE和ORDER BY连起来看满足最佳左前缀原则也是可以的
  • 排序字段要么都升序要么都降序,如果一升一降会产生FileSort
  • 排序中有不是索引的列会使用FileSort

GROUP BY的本质是先排序,后分组,所以遵循和ORDER BY一样的原则,只需要注意一点,当可以在WHERE中添加的限定条件,不要写在HAVING中

慢查询日志

MySQL的慢查询日志是MySQL提供的一种日志记录,默认关闭,用来记录MySQL中响应时间超过阈值的语句,具体时间超过long_query_time则被记录到慢查询日志中,long_query_time的默认值是10,意思是运行10秒以上的语句

#查询慢查询日志是否开启
SHOW VARIABLES LIKE '%slow_query_log%'
#开启慢查询日志,只对当前数据库生效,重启后失效,想要永久开启需要修改配置文件(my.cnf)
SET GLOBAL slow_query_log=1
#为慢日志文件指定位置和文件名
SET GLOBAL slow_query_log_file='C:\ProgramData\MySQL\MySQL Server 5.5\Data\slow20190901.log'
Variable_name Value
slow_query_log OFF
slow_query_log_file C:\ProgramData\MySQL\MySQL Server 5.5\Data\ZJZL-0190630XJ-slow.log

系统会给一个默认的host_name_slow.log,可以通过slow_query_log_file来设置

#查看当前阈值,多少秒会被记录到慢日志中。
#判断逻辑是大于,刚好等于阈值不会记录到慢日志中
SHOW VARIABLES LIKE '%long_query_time%'
#修改慢SQL阈值
SET GLOBAL long_query_time=3
Variable_name Value
long_query_time 10.000000
#查看当前有多少条慢SQL
SHOW GLOBAL STATUS LIKE '%slow_queries%'

mysqldumpslow日志分析工具

mysqldumpslow–help查看帮助信息

  • -s
    是按照何种方式排序,后面跟ctrl
    c:访问次数
    t:查询时间
    r:返回记录
    l:锁定时间
    al:平均锁定时间
    ar:平均返回记录数
    at:平均返回查询时间
  • -t
    后面跟一个数字,表示返回前多少条符合条件的数据
  • -g
    后面接一个正则表达式
得到返回记录数最多的前十条SQL
mysqldumpslow -s r -t 10/慢日志路径
得到按时间排序最长的且有左连接查询的前十条数据
mysqldumpslow -s t -t 10 -g"left join"/慢日志路径

Show Profile

是MySQL提供的可以用来分析当前会话中语句执行的资源消耗情况,默认关闭,并保存最近15次的运行结果

#查看是否开启show profile
SHOW VARIABLES LIKE'%profiling%'
#开启show profile
SET GLOBAL profiling=1
#使用show profiles
SHOW profiles

我们可以看到每一条被记录的SQL的执行时间

QUERY_ID Duration Query
1 0.00190025 show variables like’%profiling%’

通过下面语句,在字段QUERY后面写上对应的QUERY ID可以得到具体的每一个执行流程的时间

show profile主要参数

  • ALL
    显示所有开销信息
  • BLOCK IO
    显示块IO相关开销
  • CONTEXT SWITCHES
    上下文切换相关开销
  • CPU
    显示CPU相关开销信息
  • IPC
    显示发送和接收相关开销信息
  • MEMORY
    显示内存相关开销信息
  • PAGE FAULTS
    显示页码错误相关开销信息
  • SOURCE
    显示Source_function,Source_file,Source_line相关开销信息
  • SWAPS
    显示交换次数相关开销的信息
SHOW PROFILE CPU,BLOCK IO FOR QUERY 1

主要关注下面这些参数的时间,出现任意一个都是必须要进行优化的

  • Converting HEAP to MyISAM
    查询结果太大,内存不足,需要把查询结果搬运到磁盘中
  • Creating temp table
    创建临时表:把数据拷贝到临时表中,用完再删除临时表
  • Copy to temp table on disk
    把内存中的数据拷贝到磁盘中,非常危险的情况
  • Locked
    加锁

全局查询日志

可以记录所有的SQL语句,并存储在系统表中,永远不要再生产环境开启这个功能

#开启全局查询日志
SET GLOBAL general_log =1
#以表的形式
SET GLOBAL log_output='TABLE'
#查看全局查询日志
SELECT * FROM mysql.general_log

MySQL锁机制

锁的分类

对数据的操作类型划分

  • 读锁
    针对同一份数据,多个读操作可以同时进行而不会互相影响
  • 写锁
    当前写操作没有完成前,会阻断其他的写锁和读锁

对数据操作的粒度划分

  • 行锁
  • 表锁
表锁

偏向MyISAM存储引擎,开销小,加锁快,不会出现死锁,但是锁的粒度大,发生锁冲突的概率高,并发度很低

#手动添加表锁(读锁|写锁)
LOCK TABLE   [tableName [READ|WRITE]] , [ ] , [ ]……
#查看表上加过的锁
SHOW OPEN TABLES
#释放表锁
UNLOCK TABLES

表读锁
加锁session可以读加锁表,不可以写(报错),不可以读写其他表(报错),只读不可写
其他session可以读加锁表,不可以写(阻塞),可以读写其他未加锁表,可读写阻塞
表写锁
加锁session 可以读加锁表,可以写加锁表,不可以读写其他表,可读可写
其他session 不可以读(阻塞),不可以写,可以读写其他表,读阻塞不可写

  • 加锁session只可以处理自己加锁的表
  • 读锁阻塞其他session的写操作,加锁session不可以写
  • 写锁阻塞其他session的所有操作,加锁session可读可写
行锁

偏向InnoDB存储引擎,开销大,加锁慢,可能会出现死锁,但是锁的粒度最小,发生锁冲突的概率最小,并发度最高

InnoDB存储引擎默认的事务隔离级别是不可重复读,这种隔离级别下的行锁有以下特点

  • 加锁session可以写,可以读最新的、未提交的数据,读己之所写
  • 其他session读不到未提交的数据,并且阻塞对同一行的写操作

索引失效会导致行锁升级为表锁
间隙锁
当我们用范围条件而不是等值条件来给数据加锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但是并不存在的数据被称为间隙。InnoDB也会对这个间隙加锁,这种锁机制就叫间隙锁。
间隙锁会导致范围内,不存在的键值也被锁定,所以在锁定后无法插入键值范围内的任何数据,某些情况会影响性能

如何锁定一行
使用关键字FOR UPDATE,直到提交才会释放锁

SELECT * FROM tableName WHERE id=1 FOR UPDATE

主从复制

slave会从master读取binlog来进行数据同步,master将改变记录到二进制文件中,这些记录过程叫做二进制日志事件,slave将maser的二进制文件拷贝到它的中继日志中,slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL的主从复制是,异步且串行化,所以会有延迟的问题。注意:

  • 每个slave只能有一个master
  • 一个master可以有多个slave
  • 每个slave只能有一个唯一的服务器ID

主机修改配置文件my.ini

  • server-id 必须
    这是服务器的唯一ID
  • log-bin 必须
    启用二进制日志
    本地路径/mysqlbin
  • log-err 可选
    启用错误日志
    本地路径、mysqlerr
  • binlog-ignore-db 可选
    设置不需要复制的数据库
    后面写数据库的名字
  • binlog-do-db 可选
    设置需要复制的数据库

从机只需要配置服务器ID即可

在主机上建立账户并授权给从机

  • CRANT REPLICATION SLAVE ON *.* ‘userName’ @ ‘主机IP’ IDENTIFIED BY ‘passWord’
    把userName,passWord替换成想要建立的账户,使用MySQL的命令行来执行
  • flush privileges
    刷新
  • show master status
    记录File和Postion的值,记录要复制哪一个二进制文件和要开始复制的位置,注意,这时候不要操作主机,否则这两个值会变化
  • 在从机上配置完成配置
    CHANGE MASTER TO MASTER_HOST=‘主机IP’,MASTER_USER=‘userName’,MASTER_PASSWORD=‘passWord’,MASTER_LOG_FILE=’’,MASTER_LOG_POS=
  • STARE SLAVE
    启动主从复制服务

配置完成后使用SHOW SLAVE STATUS命令,如果SLAVE_IO_RUNNING
SLAVE_SQL_RUNNING同为YES则表示配置成功

你可能感兴趣的:(MySQL)