1 sql优化步骤
1.1 查看sql执行频率
show status like 'Com_______'; # 7个下划线,代替一个字符,查看当前连接的统计结果
show global status like 'Com_______'; # 7个下划线,代替一个字符,查看当前连接的统计结果
show [global] status like 'Innodb_rows_%'; #查看所有innodb引擎的状态
1.2 定位低效率执行SQL
- 慢查询日志(后面再详细说)
- show processlist:该命令可以查看当前mysql正在进行的线程,包括线程的状态、是否锁表等,可以实时的查看sql的执行情况,同时对一些锁表操作进行优化。
show processlist;
可以定位到当前慢查询的sql语句,Sengding data。
1.3 explain分析执行计划
查询到效率低的sql语句后,可以用过explain或者desc命令获取mysql如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序。
explain select * from tb_item where id=1;
列名 | 说明 |
---|---|
id | 执行编号,标识select所属的行。如果在语句中没子查询或关联查询,只有唯一的select,每行都将显示1,从上到下顺序执行。否则,内层的select语句一般会顺序编号,对应于其在原始语句中的位置,序号越大优先级越高,先执行 |
select_type | 显示本行是简单或复杂select。如果查询有任何复杂的子查询,则最外层标记为PRIMARY(DERIVED、UNION、UNION RESUlT) |
table | 访问引用哪个表(引用某个查询,如“derived3”) |
type | 数据访问/读取操作类型(ALL、index、range、ref、eq_ref、const/system、NULL) |
possible_keys | 揭示哪一些索引可能有利于高效的查找 |
key | 显示mysql决定采用哪个索引来优化查询(若为null表示没有用索引) |
key_len | 显示mysql在索引里使用的字节数 |
ref | 显示了之前的表在key列记录的索引中查找值所用的列或常量 |
rows | 为了找到所需的行而需要读取的行数,估算值,不精确。通过把所有rows列值相乘,可粗略估算整个查询会检查的行数 |
Extra | 额外信息,如using index、filesort等 |
1.4 show profile分析SQL
- 查看mysql是否支持
select @@have_profiling;
- 查看是否开启profile
select @@profiling;
为0表示当前为开启
- 为本次会话开启支持(仅当前登录有效)
set profiling=1
- 输入任意sql,profile会记录时间
- 查看sql执行的时间
show profiles;
- 查询具体sql执行过程中每个线程的状态和消耗的时间(all可以查看所有信息,包括cpu等,也可以把all换成cpu等只查看cpu等信息)
show profile [all] for query query_id;
Sending data状态表示mysql线程开始访问数据行并把结果返回给客户端整个过程执行的时间。
1.5 trace分析优化器执行计划
通过trace文件能够进一步了解为什么优化器选择A计划而不是选择B计划。
打开trace,设置格式为JSON,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存国小而不能够完整展示。
set optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
执行sql语句,然后通过以下指令查看
select * from information_schema.optimizer_trace\G;
2 索引的使用
使用索引可以加快查询的速度,可以为经常查询的字段添加索引。
create index index_name on table_name(name,stauts...);
2.1 避免索引失效
- 全值匹配,对索引中所有列都指定具体值,该情况下,索引生效,执行效率高。
最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。范围查询右边的列,不能使用索引。
不要在索引列上进行运算操作,索引将失效。
字符串不加单引号,造成索引失效(由于存在类型转换,相当于进行了运算操作)。
尽量使用覆盖索引,避免select *,查询列超出索引列,也会影响性能
TIP:
using index:使用覆盖索引的时候会出现
using where:在查找使用索引的情况下,需要回表去查询所需的数据
using index condition:查找使用了索引,但是需要回表查询数据
using index;using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
用or分割开的条件,如果or前的条件中列有索引,二后面的列中没有索引,那么涉及的索引都不会被用到。及or前有索引,or后无索引,则最后查询不走索引(and会走索引)
以%开头的Like模糊查询,索引失效(可以通过覆盖索引来解决问题,使用索引)
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效如果mysql评估使用索引比全表更慢,则不使用索引
is null,is not null 有时索引失效
in走索引,not in索引失效。
单列索引和复合索引
尽量使用复合索引,而少使用单列索引。(最左前缀原则)
单列索引mysql只会选择最优的索引(辨识度最高的索引),而不会使用全部的索引。
2.2 查看索引的使用情况
show [global] status like 'Handler_read%';
3 SQL优化
3.1 大批量插入数据
使用 load 命令导入数据的时候,适当的设置可以提高导入的效率。
load data local infile 'file_path' into table `table_name` fields terminated by ',' lines terminated by '\n';
对于InnoDB类型的表,有以下几种方式可以提高导入的效率:
- 主键顺序导入
因为InnoDB类型的表是按照主键的顺序保存的,所以当导入的数据按照主键的顺序排序,可以提高效率。 - 关闭唯一性校验
在导入数据前执行SET UNIQUE_CHECKS=0
关闭唯一性校验,再导入结束后执行SET UNIQUE_CHECKS=1
,恢复唯一性校验,可以提高效率。 - 手动提交事务
在导入数据前执行SET AUTOCOMMIT=0
关闭自动提交,再导入结束后执行SET AUTOCOMMIT=1
,恢复自动提交,可以提高效率。
3.2 优化insert语句
- 将多条insert语句改成1条insert语句
- 在事务中进行数据插入(相当于暂时关闭自动提交)
- 数据有序插入
3.3 order by优化
尽量减少格外的排序,通过索引直接返回有序数据。
类似于覆盖索引,select返回的结果只查询有索引的字段,这样可以通过索引排序,提升效率,否则将通过文件排序。
按照多个字段进行排序时,最好全升序或者全降序。
3.4 group by优化
可以在最后加上order by null禁止排序来提升效率。
也可以通过创建索引提高效率。
3.5 子查询优化
使用多表联查替换子查询
3.6 or优化
对于包含or的人查询子句,如果要利用索引,则or之间的每个条件列都必须用到单列索引,而且不能使用到复合索引;如果没有索引,则应该考虑增加索引。建议使用union替换or。
3.7 优化分页查询
一般分页查询时,通过创建覆盖索引能够比较好的提高性能。一个常见的问题是 limit 2000000,10,此时mysql排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化:
- 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容,如:
select * from item t, (select id from item order by id limit 2000000,10) a where t.id=a.id;
- 该方案适用于主键自增的表,可以把limit查询转换成某个位置的查询,但如果主键出现断层则不准确了,如:
select * from item where id>2000000 limit 10;
3.8 使用sql提示
即在sql语句中加入一些人为的提示来达到优化操作的目的。
使用use index或者ignore index强制指定索引。或者通过force index强制使用索引。
4 应用优化
4.1 使用数据库连接池
4.2 减少对mysql的访问
- 避免对数据进行重复检索
- 增加cache层
4.3 负载均衡
- 利用mysql复制分流查询(mysql主从复制)
- 采用分布式数据库架构
5 mysql内存管理及优化
5.1 内存优化原则
- 将尽量多的内存分配给mysql做缓存,但是要给操作系统和其他程序预留足够的内存。
- myisam存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此,如果有myisam表,就要预留更多的内存给操作系统做IO缓存。
- 排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认值的设置要根据最大连接数合理分配,如果设置太大,不但浪费资源,而且在并发连接较高时会导致物理内存耗尽。
5.2 MyISAM内存优化
MyISAM存储引擎使用key_buffer缓存索引块,加速MyISAM索引的读写速度。对于MyISAM表的数据块,mysql没有特别的缓存机制,完全依赖于操作系统的IO缓存。
key_buffer_size
该参数决定MyISAM索引块缓存区的大小,直接影响到MyISAM表的存取效率。可以在MySQL参数文件中设置,一般建议至少将1/4可用内存分配给该参数。
在/usr/my.cnf中进行配置,如:
key_buffer_size=512M
read_buffer_size
如果需要经常顺序扫描myisam表,可以通过增大read_buffer_size的值来改善性能。但需要注意的是read_buffer_size是每个session独占的,如果设置太大,会造成内存浪费。
read_rnd_buffer_size
如果需要做排序的myisam表的查询,如带有order by子句的sql,适当增加read_rnd_buffer_size的值,可以改善此类的sql性能。但需要注意的是read_rnd_buffer_size是每个session独占的,如果设置太大,会造成内存浪费。
5.2 InnoDB内存优化
Innodb用一块内存区做IO缓存池,该缓存池不仅用来缓存innodb的索引块,而且也用来缓存innodb的数据块。
innodb_buffer_pool_size
该参数决定了innodb存储引擎表数据和索引数据的最大缓存区大小。在保证操作系统和其他程序有足够内存可用的情况下,该值越大,缓存命中率越高,访问innodb表需要的磁盘i/o就越少,性能也就越高。
innodb_buffer_pool_size=512M
innodb_log_buffer_size
决定了innodb重做日志缓存的大小,对于可能产生大量更新记录的大事物,增加该参数大小,可以避免innodb在事务提交前就执行不必要的日志写入磁盘操作
innodb_log_buffer_size=10M
6 Mysql并发参数调整
在mysql中,控制并发连接和线程的主要参数包括max_connections、back_log、thread_cache_size、table_open_cache。
6.1 max_connections
该参数控制允许连接到mysql数据库的最大数量,默认值是151。如果状态变量connect_errors_max_connections不为零,并且一直增长,则说明不断有连接请求因数据库连接数已达到允许最大值而失败,这时可以考虑增加max_connections的值。
mysql最大可支持的连接数,取决于很多因素,包括内存大小,每个连接负荷,cpu处理速度等。所以不是设置的越大越好。
6.2 back_log
该参数控制mysql监听tcp端口时设置的积压请求栈大小。如果mysql的连接数达到max_connections,新来的请求将会存在堆栈中,该堆栈数量即为back_log,如果等待连接的数量超过back_log,将不被授予连接资源,将会报错。如果需要短时间内处理大量请求,可以适当增大一点。
6.3 table_open_cache
该参数控制所有sql语句执行线程可打开表缓存的数量,而在执行sql语句时,每一个sql执行线程至少要打开1个表缓存。应根据最大连接数max_connections以及每个连接执行的关联查询中涉及的表的最大数量来设定。
6.4 thread_cache_size
为了加快连接数据库速度,mysql会缓存一定数量的客户服务线程以备重用,通过该参数可控制缓存客户服务线程的数量
6.5 innodb_lock_wait_timeout
该参数用来设置innodb事务等待行锁的时间,默认50ms。对于需要快速反馈的业务系统来说,可以将行锁的等待时间调小,以免事务长时间挂起;对于后台运行的批量处理程序来说,可以调大,以避免发生大的回滚操作。
7 mysql锁
7.1 锁分类
从对数据操作的粒度分:
- 表锁:操作时,会锁定整张表
- 行锁:操作时,会锁定当前操作行
从对数据操作的类型分: - 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
- 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁
7.2 mysql锁
存储引擎 | 表级锁 | 行级锁 | 页面锁 |
---|---|---|---|
MyISAM | 支持 | 不支持 | 不支持 |
InnoDB | 支持 | 支持 | 不支持 |
MEMORY | 支持 | 不支持 | 不支持 |
BDB | 支持 | 不支持 | 支持 |
表级锁: 偏向MyISAM存储引擎,开销小,加锁快;不会出现死锁;锁定粒度大,发生所冲突的概率最高,并发度最低。
行级锁:偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定力度小,发生锁冲突概率最低,并发度最高。
页面所:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定力度介于表锁和行锁之间,并发度一般。
7.3 MyISAM表锁
7.3.1 如何加锁
只支持表锁
select前,自动给涉及的表加读锁,执行(update、delete、insert等)前,自动给涉及的表加写锁,该过程不需要用户干预,因此一般不需要直接用lock table命令显示加锁。
显示加表锁语法:
加读锁:lock table table_name read;
加写锁:lock table table_name write;
释放锁:unlock tables;
读锁会阻塞写,但不会阻塞读。写锁既会阻塞读,又会阻塞写。
7.3.2 查看锁的争用情况
show open tables; 查看每张表的情况
show status like 'Table_locks%'; 查看所有表汇总的情况
7.4 InnoDB行锁
事务四大特性ACID
原子性(Atomicity):事务是一个原子操作但愿,其对数据的修改,要么全部成功,要么全部失败。
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。
隔离性(Isolation):数据库系统提供移动的隔离机制,保证事务在不受外部并发操作影响的“独立”环境下运*行。
持久性(Durable):事务完成之后,对于数据的修改是永久的。并发事务处理带来的问题
丢失更新:当两个或多个事务选择同一行,最初的事务修改的值,会被后面的事务修改的值覆盖。
脏读:当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,然后使用了这个数据。
不可重复读:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现和以前读出的数据不一致。
幻读:一个事务按照相同的查询条件重新读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据。事务的隔离级别
隔离级别 | 丢失更新 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
read uncommitted | × | √ | √ | √ |
read committed | × | × | √ | √ |
repeatable read (default) | × | × | × | √ |
serializable | × | × | × | × |
查看当前的隔离级别:
mysql5: show variables like 'tx_isolation';
mysql8: show variables like 'transaction_isolation';
无索引行锁升级为表锁
索引失效或者无索引会使行锁升级成表锁间隙锁
当用范围条件,而不是使用相等条件检索数据,并请求共享或排它锁时,InnoDB会给符合条件的已有数据进行加锁;对于键值在条件范围内但并不存在的记录,叫做”间隙(GAP)“,InnoDB也会对这个”间隙“加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。(如:查询id<4,其中id=2记录不存在,则会给id=2加上间隙锁)。行锁征用情况
show status like 'innodb_row_lock%';
8 常用的sql技巧
- sql执行顺序
编写顺序:
执行顺序: