MySQL性能优化
MySQL优化思路
1. 1 优化的范围有哪些
存储、主机和操作系统方面:
1)主机架构稳定性
2)I/O规划及配置
3)Swap交换分区
4)OS内核参数和网络问题
应用程序方面:
1)应用程序稳定性
2)SQL语句性能
3)串行访问资源
4)性能欠佳会话管理
5)这个应用适不适合用MySQL
数据库优化方面:
1)内存
2)数据库结构(物理&逻辑)
3)实例配置
MySQL的单表优化总结
1.1 数据库设计和在单表创建时就要考虑性能问题
很多时候当我们数据表创建好并已经使用了一段时间之后才发现有性能问题,比如字段长度不合理,字段类型不能满足应用程序需要,单表太大导致需要进行分表等等,这些前期所导致表的不合理设计在我们应用程序使用后再去调整的时候会很被动,并且代价很大,所以在设计表阶段就需要进行良好的分析和合理的设计,等到我们表真正遇到性能问题时候再去考虑如何进行优化。
1.2 “字段”优化总结
- 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED。
- VARCHAR的长度只分配真正需要的空间。
- 使用枚举或整数代替字符串类型。
- 尽量使用TIMESTAMP而非DATETIME,TIMESTAMP空间消耗少,操作方便。如果在MySQL 5.6.5版本之前,自动初始化和自动更新(Automatic Initialization and Updating)只适用于TIMESTAMP。
区别 DATETIME TIMESTAMP 存储时间范围 1001年 ~ 9999年 1970年 ~ 2038年 空间消耗 8字节 4字节 时区相关 不相关 相关 默认值 没有 当前时间戳 精度 秒 秒 - 单表不要有太多字段,建议在20个以内。
- 避免使用NULL字段,很难查询优化且占用额外索引空间。
- 用整型来存IP地址。
1.3 “索引”优化总结
- 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描。
- 应尽量避免在WHERE子句中对字段进行NULL值判断,Null值会导致mysql优化器处理起来比较复杂,有可能将导致引擎放弃使用索引而进行全表扫描。
- 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段。
- 字符字段只建前缀索引。
- 字符字段最好不要做主键。
- 不用外键,由程序保证约束。
- 如果可以尽量不用UNIQUE唯一索引,由程序保证唯一约束。
- 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引。
1.4 “查询SQL”优化总结
- 1)可通过开启慢查询日志来找出较慢的SQL。
- 2)不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边。
- 3)sql语句尽可能简单,一条sql只能在一个cpu运算。大语句拆小语句,减少锁时间。一条大sql可以堵死整个库。
- 4)不用SELECT * 查询,使用星号(
*
) 在MySQL数据库服务器和应用程序之间产生不必要的I/O磁盘和网络流量,需要什么字段就取什么字段。同时SELECT * 要对表中所有列进行权限检查,这部分也是开销。 - 5)OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,IN的个数建议控制在200以内。
- 6)不用函数和触发器,在应用程序实现。
- 7)避免%xxx式查询。
- 8)少用JOIN连接查询,可以反范式化设计,以空间换时间。
- 9)使用同类型进行比较,避免隐式类型转换(当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容,则会发生转换隐式,会造成索引失效问题),比如用'123'和'123'比,123和123比,如果123和'123'比较,MySQL会根据需要自动将数字转换为字符串,将字符串转换数字。官网https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html有说明隐式类型转换问题。
- 10)尽量避免在WHERE子句中使用!= 或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
- 11)对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5。
- 12)列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大。
1.5 “存储引擎”的选择
目前广泛使用的是MyISAM和InnoDB两种引擎,在Mysql8中已经废弃了MyISAM存储引擎,MyISAM不建议使用。https://www.percona.com/blog/2016/10/11/mysql-8-0-end-myisam/
MyISAM之所以被Mysql8废弃,主要还是因为他一些弱项,例如 不支持事务、表级锁、没有crash恢复,而且他的优点已经逐渐被 InnoDB 实现了。
如何查看你当前Mysql使用的是哪个InnoDB版本,使用查询语句SHOW GLOBAL VARIABLES LIKE 'innodb_ver%'; 如下
因为我的Mysql版本是5.7.30,所以InnoDB和Mysql版本一致。
他们的区别如下:
【MyISAM】:
MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
- 1)不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁。
- 2)不支持事务。
- 3)不支持外键。
- 4)不支持崩溃后的安全恢复。
- 5)在表有读取查询的同时,支持往表中插入新纪录。
- 6)支持BLOB和TEXT的前500个字符索引,支持全文索引。
- 7)支持延迟更新索引,极大提升写入性能。
- 8)对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用。
【InnoDB】:
InnoDB在MySQL 5.5后成为默认索引,它的特点是:
- 1)支持行锁,采用MVCC来支持高并发。
- 2)支持事务。
- 3)支持外键。
- 4)支持崩溃后的安全恢复。
- 5)不支持全文索引。
总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表。
Mysql服务器系统参数调优
1.1 基准测试工具:
- sysbench:一个模块化,跨平台以及多线程的性能测试工具;
- iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具;
- tpcc-mysql:Percona开发的TPC-C测试工具。
1.2 具体的调优参数
- 1)back_log:
back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。MySQL每处理一个连接请求的时候都会对应的创建一个新线程与之对应,那么在主线程创建新线程期间,如果有大量的短连接请求到达数据库时,MySQL 会限制此刻新的连接进入请求队列,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即由参数back_log
控制,如果等待的连接数量超过back_log
,则将不会接受新的连接请求,所以如果需要MySQL能够处理大量的短连接,需要提高此参数的大小。back_log,如果等待连接的数量超过back_log,将不被授予连接资源。
查看back_log
- 2)wait_timeout:
客户端在连接到数据库后如果太长时间没动静,连接器就会自动将它断开,这个时间是由参数wait_timeout
控制的,默认值是8小时。数据库连接闲置时间,闲置连接会占用内存资源,可以从默认的8小时减到半小时。查看
- 3)max_user_connection:
该参数作用是设置单个用户最大连接数限制。具体设置多少,要根据实际情况再裁定,默认为0无上限,最好设一个合理上限。 - 4)thread_concurrency:
控制并发线程数,thread_concurrency变量是针对于Solaris 8及低版本的系统使用。设为CPU核数的两倍,比如有一个双核的CPU, 那么thread_concurrency的应该为4,不过错误设置了thread_concurrency的值, 会导致mysql不能充分利用多cpu(或多核),所以设置该值也得根据具体情况。
https://www.percona.com/blog/2012/06/04/thread_concurrency-doesnt-do-what-you-expect/
这个参数已经在最新版本的mysql中被移除了,官方文档说明 - 5)skip_name_resolve:
禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问。 - 6)key_buffer_size:
索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好。 - 7)innodb_buffer_pool_size:
缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好。 - 8)innodb_additional_mem_pool_size:
InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小; - 9)innodb_log_buffer_size:
InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB。 - 10)query_cache_size:
缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大。 - 11)可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小。
- 12)read_buffer_size:
MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能。 - 13)sort_buffer_size:
MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。 - 14)read_rnd_buffer_size:
MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。 - 15)record_buffer:
每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值。 - 16)thread_cache_size:
保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的。 - 17)table_cache:
类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM。
硬件升级
Scale up,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能。
表分区
MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码。
分区的过程是将一个表或索引分解为多个更小、更可管理的部分。对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引。
如何判断是否支持表分区:
分区的类型:
- 1)RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区;
- 2)LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择;
- 3)HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式;
- 4)KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
分区的好处是:
- 1)可以让单表存储更多的数据;
- 2)分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作;
- 3)部分查询能够从查询条件确定只落在少数分区上,速度会很快;
- 4)分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备;
- 5)可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争;
- 6)可以备份和恢复单个分区。
分区的限制和缺点:
- 1)一个表最多只能有1024个分区;
- 2)如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来;
- 3)分区表无法使用外键约束;
- 4)NULL值会使分区过滤无效;
- 5)所有分区必须使用相同的存储引擎。
分区最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示:
CREATE TABLE members ( firstname VARCHAR(25) NOT NULL, lastname VARCHAR(25) NOT NULL, username VARCHAR(16) NOT NULL, email VARCHAR(35), joined DATE NOT NULL ) PARTITION BY RANGE( YEAR(joined) ) ( PARTITION p0 VALUES LESS THAN (1960), PARTITION p1 VALUES LESS THAN (1970), PARTITION p2 VALUES LESS THAN (1980), PARTITION p3 VALUES LESS THAN (1990), PARTITION p4 VALUES LESS THAN MAXVALUE );
查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。
如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存。
在mysql5.7的早期版本中,分区表的实现性能较差,与相同数据量的单表相比性能下降约10%。后来在mysql5.7.19才做了优化,可以去http://bugs.mysql.com上面看看相关bug。但是即使这个bug修复之后,分区表仍然比相同数据量的单表有大约5%的性能下降