MySQL优化--概述以及索引优化分析
三、查询截取分析
3.1、查询分析方法
-
观察,至少跑1天,看看生产的慢SQL情况。
-
开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
-
explain+慢SQL分析
-
show profile
-
运维经理 or DBA,进行SQL数据库服务器的参数调优。
==总结
-
慢查询的开启并捕获
-
explain+慢SQL分析
-
showprofile查询SQL在Mysq1服务器里面的执行细节和生命周期情况
-
SQL数据库服务器的参数调优。
3.2、in和exists
EXISTS语法:
SELECT ... FROM table WHERE EXISTS (subquery)
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。
- EXISTS (subquery)只返回TRUE或FALSE,因此子查询中的SELECT后可以是
SELECT 1
或select 'X'
,官方说法是实际执行时会忽略SELECT清单,因此没有区别 - EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比。
- EXISTS子查询往往也可以用条件表达式、其他子查询或者JOIN来替代,何种最优需要具体问题具体分析I
优化原则:
-
小表驱动大表,即小的数据集驱动大的数据集。
-
当B表的数据集必须小于A表的数据集时,用in优于exists。
-
当A表的数据集系小于表的数据集时,用exists优于in。
3.3、提高Order By的速度
-
Order by时select *是一个大忌,只Query需要的字段,这点非常重要。 在这里的影响是:
1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是TEXT|BLOB类型时,会用改进后的算法——单路排序,否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量, 超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size 。
-
尝试提高sort_buffer_size
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 -
尝试提高max_length_for_sort_data
提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率。
3.4、Group By
-
group by实质是先排序后进行分组,遵照索引建的最佳左前缀
-
当无法使用索引列,增大max_ length_ for_ sort_data参数的设置+增大sort_buffer_size参数的设置
-
where高于having,能写在where限定的条件就不要去having限定了。
3.5、慢查询日志
3.5.1、慢查询日志开启
通过如下语句查看开启状态
show variables like '%slow_query_log%';
开启方法
set global slow_query_log = 1;
注意:
使用上面的语句开启慢查询日志只对当前数据库生效,重启MySQL失效。
如果需要永久生效,修改my.cnf
/my.ini
后重启MySQL
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/${host_name}-slow.log
尽量不要这么做,降低性能!!!
3.5.2、慢查询界定时间
查询
show variables like '%long_query_time%';
修改
set global long_query_time = 3;
判断是大于long_query_time,而非大于等于
设置之后需要重新连接或新开一个会话才能看到修改的值
3.5.3、查询当前系统中有多少慢查询SQL
show global status like '%slow_queries%';
3.6、通过mysqldumpslow查询
mysqldumpslow [ OPTS... ] [ LOGS... ]
s:是表示按照何种方式排序;
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:返回前面多少条的数据
g:后面搭配一个正则匹配模式,大小写不敏感
3.7、profiles工具
3.7.1、使用方法
查看状态
show variables like 'profiling';
查看结果
show profiles;
诊断SQL
show profile cpu,block io for query ${Num};
上面为常用参数
参数:
类型 | 含义 |
---|---|
ALL | 所有的开销信息 |
BLOCK IO | 块IO相关开销 |
CONTEXT SWITCHES | 上下文切换相关开销 |
CPU | CPU相关开销信息 |
IPC | 发送和接收相关开销信息 |
MEMORY | 内存相关开销信息 |
PAGE FAULTS | 页面错误相关开销信息 |
SOURCE | Source_function,Source_file, Source_ line相关开销信息 |
SWAPS | 交换次数相关开销的信息 |
3.7.2、总结
执行上面的SQL后尽量不出现下面的东西
- converting HEAP to MyISAM查询结果太大,内存都不够用了往磁盘上搬了。
- Creating tmp table创建临时表,拷贝数据到临时表,用完在删
- Copying to tmp table on disk 把内存中临时表复制到磁盘,危险! ! !
- locked
3.8、全局查询日志
永远不要在生产环境使用
方法一、命令
开启
set global general_log=1;
设置输出方式
set global log_output='TABLE'
此后,你所编写的sq|语句,将会记录到mysq|库里的general_log表, 可以用下面的命令查看
查看方式
selet * from mysql.general_log;
方法二、配置文件启用
在MySQL的my.cnf
/my.ini
中添加如下
# 开启
general_log=1
# 记录日志文件的路径
general_log_file=/path/logfile
# 输出格式
log_output=FILE
四、表锁(MyISAM)
4.1、锁的分类
- 锁定表了之后不可以对未锁定的表做操作
按数据类型分类:
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会相互影响
写锁(排他锁):当写操作没有完成前,阻断其他写锁和读锁
按对数据操作的粒度分:
表锁;
行锁;
4.2、手动操作锁
添加表锁
lock table 表名 read(write), 表名2 read(write), 其他
查看表上加过的锁
show open tables;
解锁
unlock tables;
4.3、分析表锁定
通过检查table_locks_waited
和table_locks_immediate
状态来分析系统上的表锁定
show status like 'table%';
这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况;
此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
五、事务(InnoDB)
5.1、事务的ACID
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一 定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
5.2、并发处理带来的问题
更新丢失(Lost Update):两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失的;
脏读(Dirty Reads):读取到了已修改尚未提交的数据
不可重复读(Non-Repeatable Reads):在同一事务中,两次读取同一数据,得到内容不同,也就是有其他事务更改了这些数据
幻读(Phantom Reads):一个事务在执行过程中读取到了另一个事务已提交的插入数据;即在第一个事务开始时读取到一批数据,但此后另一个事务又插入了新数据并提交,此时第一个事务又读取这批数据但发现多了一条,即好像发生幻觉一样。
5.3、MySQL的事务隔离级别
事务隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
读未提交(read-uncommitted) | 最低级别,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 |
读已提交(read-committed) | 语句级 | 否 | 是 | 是 |
可重复读(repeatable read) | 事务级(MySQL默认) | 否 | 否 | 是 |
可序列化(serializable) | 最高级别,事务级 | 否 | 否 | 否 |
查看数据库的隔离级别
# MySQL5.x
show variables like '%tx_isolation%';
# MySQL8.0
show variables like '%transaction_isolation%';
六、行锁(InnoDB)
在InnoDB中索引失效,行锁变表锁
6.1、锁定某一行
默认未提交前锁定一行
语法
# 显式开启事务
begin;
select * from table_name where xxxx for update;
# 提交事务
commit;
关闭自动提交
# 1为自动提交 set autocommit=0;
操作同一行数据会阻塞
操作不同行数据不受影响
注意:如果关闭自动提交了之后,查询的数据为关闭自动提交时的快照
6.2、查看行锁争夺情况
show status like 'innodb_row_lock%';
状态量 | 解释 |
---|---|
InnoDB_row_lock_current_waits | 当前正在等待锁定的数量(重要) |
InnoDB_row_lock_time | 从系统启动到现在锁定总时间长度(重要) |
InnoDB_row_lock_time_avg | 每次等待所花平均时间; |
InnoDB_row_lock_time_max | 从系统启动到现在等待最常的一次所花的时间 |
InnoDB_row_lock_waits | 系统启动后到现在总共等待的次数(重要) |
6.3、间隙锁的危害
6.3.1、什么是间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,
InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁) 。
6.3.2、危害
因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
6.4、总结
InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些, 但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。
但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会证Innodb的整体性能表现不仅不能比MyISAM高甚至可能会更差。
优化建议
-
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
-
合理设计索引,尽量缩小锁的范围
-
尽可能较少检索条件,避免间隙锁
-
尽量控制事务大小,减少锁定资源量和时间长度
-
尽可能低级别事务隔离