现如今,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显,所以要重视数据库的性能优化。
一个成熟的数据库架构并不是一开始设计就具备高可用、高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善。
SQL执行慢的原因:
根据上述问题,将数据库的优化分为几个阶段进行调整,力求让数据库发挥好的性能和稳定运行。
项目立项后,开发部门根据产品部门需求开发项目。开发工程师在开发项目初期会对表结构设计。对于数据库来说,表结构设计很重要,如果设计不当,会直接影响到用户访问网站速度,用户体验不好!
这种情况具体影响因素有很多,例如慢查询(低效的查询语句)、没有适当建立索引、数据库堵塞(锁)等。当然,有测试部门的团队,会做产品测试,找Bug。
由于开发工程师重视点不同,初期不会考虑太多数据库设计是否合理,而是尽快完成功能实现和交付。等项目上线有一定访问量后,隐藏的问题就会暴露,这时再去修改就不是这么容易的事了!
sql语句优化
1.EXPLAIN分析SELECT查询
很多情况下,使用EXPLAIN关键字可以知道MySQL是如何处理SQL语句的,这可以帮助分析查询语句,从而或许能尽快的找到优化方法以及潜在的性能问题。
2.SELECT查询必须指明字段名
SELECT * 的查询会加很多不必要的消耗(例如CPU、I/O等),同时,也有可能增加了使用覆盖索引。所以SELECT查询时,要求直接在后面指明需要查询的对应字段名。
3.查询一条数据的时候,使用 LIMIT 1
减少多余的查询,因为指定limit 1后,查询到一条数据就不再继续查询了,使得EXPLAIN中type列达到const类型,查询语句更优。
4.为搜索的WHERE字段建立索引
一般,每个表都会设置一个主键,而索引并不一定就是给主键。如果在表中,有某个字段总要会经常用来做WHERE查询搜索,而且是读大于写的。
5.千万不要使用 ORDER BY RAND()
如果想随机取数据,不要用用随机数取,因为这种查询,对数据库的性能毫无益处(消耗CPU)。更好的方案之一是先找到数据所在的条数N,然后再用LIMIT N, 1这样查询。
6.保证每张表都有一个主键ID
每设计新建一张表的时候,都应该为其设计一个ID字段,并让其成为主键,而且最好是INT型(也有使用UUID的),同时设置这个ID字段为自增(AUTO_INCREMENT)的标志。
7.尽可能的使用 NOT NULL
NULL也需要额外的空间,NULL字段在进行查询比较的时候,是比较麻烦的。如果不是必须使用NULL,就建议使用NOT NULL。
8.选择合适的存储引擎
在MySQL中有MyISAM和InnoDB两种存储引擎,两者各有利弊,需要了解两者的差异然后来做出最合适的选择,例如InnoDB支持事务而MyISAM不支持,MyISAM查询比InnoDB快等等;若不太清楚选择什么的话,那就用InnoDB。
9.把IP地址存为UNSIGNED INT
在遇到需要存储IP地址的时候,大多数想法都会是存储VARCHAR(15)字符串类型的,而不会想到要用INT整型来存储;如果用整型来存储,只需要4个字节,并且你可以有定长的字段,而且会带来查询上的优势。
10.尽量不要在WHERE查询时对字段进行null值判断
当对一个字段进行null的判断时候,会比较慢的,这是因为这个判断会导致引擎放弃使用所有已有的索引而进行全表扫描搜索。
11.尽量不要使用%前缀的LIKE模糊查询
模糊查询,在日常开发中会经常遇到,很多都是直接 LIKE ‘%key_word%’ 或者 LIKE ‘%key_word’ 这样搜索的,这两种搜索方式,都会导致索引失效从而进行全表扫描搜索。建议使用“全文索引”。
12.避免在WHERE查询时对字段进行表达式操作
例如查询语句SELECT id FROM table WHERE num * 2 = 50;,这样的查询,对字段num做了一个乘2的算数操作,就会导致索引失效。
13.减少不必要的排序
排序操作会消耗较多的CPU资源,所以减少不必要的排序可以在缓存命中率高等I/O足够的情况下,会降低SQL的响应时间。
14.建议用JOIN代替子查询
JOIN的性能相比子查询有更大优势。
15.避免发生隐式类型转换
类型转换主要是指在WHERE子句中出现字段的类型和传入的参数类型不一致的时候发生的类型转换;这是因为如果传入的数据类型和字段类型不一致,MySQL可能会对数据进行类型转换操作,也可能不进行处理而直接交由存储引擎去处理,这样一来,就可能会出现索引无法使用的情况而造成执行计划问题。
16.避免多表查询字段类型不一致
在遇到需要多表联合查询的时候,设计表结构的时候,尽量保持表与表的关联字段一致,并且都要设置索引。同时,多表连接查询时,尽量把结果集小的表作为驱动表。
17.建议开启查询缓存
大多数的MySQL服务器都开启了查询缓存,这是提高性能最有效的方法之一,因为查询缓存由MySQL数据库引擎自动处理,当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表,而直接访问缓存结果了。
18.使用UNION代替临时表
UNION查询可以把两条或更多的SELECT查询结果合并到一个查询中,从而不再需要创建临时表来完成。需要注意的是,使用UNION的所有SELECT语句中的字段数目要相同。
19.慎用IN查询
IN以及NOT IN查询都要慎重,因为可能会导致全表扫描,而对于连续的数值,能用BETWEEN就不要用IN了。
当开发人员设计好表语句后,就需要运维工程师进行服务部署,项目上线。这里应该根据需求进行预估访问量,再进行配置的选择和结构设计。
项目初期访问量一般是寥寥无几,此阶段Web+数据库单台部署足以应对在1000左右的QPS(每秒查询率)。考虑到单点故障,应做到高可用性,可采用MySQL主从复制+Keepalived实现双机热备。主流HA软件有:Keepalived(推荐)、Heartbeat。
硬盘
磁盘寻道能力(磁盘I/O),以目前高转速SCSI硬盘(7200转/秒)为例,服务器硬盘读200M/S,写120M/S。而固态硬盘读1500MB/S,写800MB/S,这个差距是非常明显的。修改磁盘调度算法为deadline,echo ‘deadline’ >/sys/block/sda/queue/scheduler,查看结果# cat /sys/block/sda/queue/scheduler
noop anticipatory [deadline] cfq
MySQL每秒钟都在进行大量、复杂的查询操作,对磁盘的读写量可想而知。所以,通常认为磁盘I/O是制约MySQL性能的最大因素之一。解决这一制约因素可以考虑以下几种解决方案:
CPU
CPU对于MySQL应用,推荐使用S.M.P.架构的多路对称CPU,例如:可以使用8颗Intel Xeon 3.6GHz的CPU。数据库对于CPU的需求没有内存这么大,通常64G内存,只需要8核CPU就可以了。如果是单实例的mysql,可以在/etc/grub.conf配置文件中,加入参数numa=off,禁用numa功能。
内存
物理内存对于一台使用MySQL的Database Server来说,服务器内存建议不要小于8GB,用以应对高速增长的咨询等信息。内存方面可以关闭swap功能, echo 0 >/proc/sys/vm/swappiness ,关闭swap功能
Linux内核有一个特性,会从物理内存中划分出缓存区(系统缓存和数据缓存)来存放热数据,通过文件系统延迟写入机制,等满足条件时(如缓存区大小到达一定百分比或者执行sync命令)才会同步到磁盘。
也就是说物理内存越大,分配缓存区越大,缓存数据越多,建议物理内存至少富裕50%以上。
MySQL应用最广泛的有两种存储引擎:一个是MyISAM,不支持事务处理,读性能处理快,表级别锁。另一个是InnoDB,支持事务处理(ACID属性),设计目标是为大数据处理,行级别锁。
表锁:开销小,锁定粒度大,发生死锁概率高,相对并发也低。
行锁:开销大,锁定粒度小,发生死锁概率低,相对并发也高。
用表锁和行锁,主要为保证数据完整性。例如,一个用户在操作一张表,其他用户也想操作这张表,那么就要等第一个用户操作完,其他用户才能操作,表锁和行锁就是这个作用。否则多个用户同时操作一张表,肯定会数据产生冲突或者异常。
根据这些方面看,使用InnoDB存储引擎是最好的选择,也是MySQL5.5+版本默认存储引擎。每个存储引擎相关运行参数比较多,以下列出可能影响数据库性能的参数。
公共参数默认值
MyISAM参数默认值
InnoDB参数默认值
大多数MySQL都部署在linux系统上,所以操作系统的一些参数也会影响到MySQL性能。
内核配置优化
打开文件句柄优化
vi /etc/security/limits.conf
数据库安全是项目中最重要的部分,信息泄露会造成重大事故,所以要重视安全问题,防止信息被盗取、破坏。
具体建议
随着业务量越来越大,单台数据库服务器性能已无法满足业务需求,该考虑增加服务器扩展架构了。主要思想是分解单台数据库负载,突破磁盘I/O性能,热数据存放缓存中,降低磁盘I/O访问频率。
给数据库增加缓存系统,把热数据缓存到内存中,如果缓存中有请求的数据就不再去请求MySQL,减少数据库负载。缓存实现有本地缓存和分布式缓存,本地缓存是将数据缓存到本地服务器内存中或者文件中。
分布式缓存可以缓存海量数据,扩展性好,主流的分布式缓存系统:memcached、redis,memcached性能稳定,数据缓存在内存中,速度很快,QPS理论可达8w左右。如果想数据持久化就选择用redis,性能不低于memcached。
在生产环境中,业务系统通常读多写少,可部署一主多从架构,主数据库负责写操作,并做双机热备,多台从数据库做负载均衡,负责读操作。主流的负载均衡器:LVS、HAProxy、Nginx。
大多数企业是在代码层面实现读写分离,效率高。另一个种方式通过代理程序实现读写分离,企业中应用较少,会增加中间件消耗。主流中间件代理系统有MyCat、Atlas等。
在这种MySQL主从复制拓扑架构中,分散单台负载,大大提高数据库并发能力。如果一台从服务器能处理1500 QPS,那么3台就能处理4500 QPS,而且容易横向扩展。
分库是根据业务将数据库中相关的表分离到不同的数据库中,例如会员库、订单库、咨询库等,每个库单独放到一个实例中。此时可以根据不同功能的压力来购买不同配置的实例,从而减少资金投入。
如果业务量很大,还可将分离后的数据库做主从复制架构,进一步避免单库压力过大。
数据量的日剧增加,数据库中某个表有几百万条数据,导致查询和插入耗时太长,应该考虑把这个表拆分成多个小表,来减轻单个表的压力,提高处理效率,此方式称为分表。
分表技术比较麻烦,要修改程序代码里的SQL语句,还要手动去创建其他表,也可以用merge存储引擎实现分表,相对简单许多。分表后,程序是对一个总表进行操作,这个总表不存放数据,只有一些分表的关系,以及更新数据的方式,总表会根据不同的查询,将压力分到不同的小表上,因此提高并发能力和磁盘I/O性能。
分表分为垂直拆分和水平拆分:
垂直拆分:把原来的一个很多字段的表拆分多个表,解决表的宽度问题。你可以把不常用的字段单独放到一个表中,也可以把大字段独立放一个表中,或者把关联密切的字段放一个表中。
水平拆分:把原来一个表拆分成多个表,每个表的结构都一样,解决单表数据量大的问题。
分区就是把一张表的数据根据表结构中的字段(如range、list、hash等)分成多个区块,这些区块可以在一个磁盘上,也可以在不同的磁盘上,分区后,表面上还是一张表,但数据散列在多个位置,这样一来,多块硬盘同时处理不同的请求,从而提高磁盘I/O读写性能。
通常使用QPS(Queries Per Second,每秒查询书)和TPS(Transactions Per Second)来查看数据库的效率。
通过show status查看运行状态,会有300多条状态信息记录,其中有几个值可以计算出QPS和TPS,如下:
Uptime:服务器已经运行的实际,单位秒
Questions:已经发送给数据库查询数
Com_select:查询次数,实际操作数据库的
Com_insert:插入次数
Com_delete:删除次数
Com_update:更新次数
Com_commit:事务次数
Com_rollback:回滚次数
基于Questions计算出QPS
mysql> show global status like 'Questions';
mysql> show global status like 'Uptime';
QPS = Questions / Uptime
基于Com_commit和Com_rollback计算出TPS:
mysql> show global status like 'Com_commit';
mysql> show global status like 'Com_rollback';
mysql> show global status like 'Uptime';
TPS = (Com_commit + Com_rollback) / Uptime
备份数据库是最基本的工作,也是最重要的。高频率的备份策略,选用一个稳定快速的工具至关重要。数据库大小在2G以内,建议使用官方的逻辑备份工具mysqldump。
超过2G以上,建议使用percona公司的物理备份工具xtrabackup,否则会很慢。这两个工具都支持InnoDB存储引擎下热备,不影响业务读写操作。
重点关注:
id:CPU利用率百分比,平均小于60%正常,但已经比较繁忙了。
wa:CPU等待磁盘IO响应时间,一般大于5说明磁盘读写量大。
KB_read/s、KB_wrtn/s 每秒读写数据量,主要根据磁盘每秒最高读写速度评估。
r/s、w/s:每秒读写请求次数,可以理解为IOPS(每秒输入输出量),是衡量磁盘性能的主要指标之一。
await:IO平均每秒响应时间,一般大于5说明磁盘响应慢,超过自身性能。
util:磁盘利用率百分比,平均小于60%正常,但已经比较繁忙了。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!