一个成熟的数据库架构并不是一开始设计就具备高可用、高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善。这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分为以下五个阶段:1、数据库表设计项目立项后,开发部根据产品部需求开发项目,开发工程师工作其中一部分就是对表结构设计。对于数据库来说,这点很重要,如果设计不当,会直接影响访问速度和用户体验。影响的因素很多,比如慢查询、低效的查询语句、没有适当建立索引、数据库堵塞(死锁)等。当然,有测试工程师的团队,会做压力测试,找bug。对于没有测试工程师的团队来说,大多数开发工程师初期不会太多考虑数据库设计是否合理,而是尽快完成功能实现和交付,等项目有一定访问量后,隐藏的问题就会暴露,这时再去修改就不是这么容易的事了。2、数据库部署该运维工程师出场了,项目初期访问量不会很大,所以单台部署足以应对在1500左右的QPS(每秒查询率)。考虑到高可用性,可采用MySQL主从复制+Keepalived做双击热备,常见集群软件有Keepalived、Heartbeat。双机热备博文:http://lizhenliang.blog.51cto.com/7876557/13623133、数据库性能优化如果将MySQL部署到普通的X86服务器上,在不经过任何优化情况下,MySQL理论值正常可以处理2000左右QPS,经过优化后,有可能会提升到2500左右QPS,否则,访问量当达到1500左右并发连接时,数据库处理性能就会变慢,而且硬件资源还很富裕,这时就该考虑软件问题了。那么怎样让数据库最大化发挥性能呢?一方面可以单台运行多个MySQL实例让服务器性能发挥到最大化,另一方面是对数据库进行优化,往往操作系统和数据库默认配置都比较保守,会对数据库发挥有一定限制,可对这些配置进行适当的调整,尽可能的处理更多连接数。具体优化有以下三个层面:3.1 数据库配置优化MySQL常用有两种存储引擎,一个是MyISAM,不支持事务处理,读性能处理快,表级别锁。另一个是InnoDB,支持事务处理(ACID),设计目标是为处理大容量数据发挥最大化性能,行级别锁。表锁:开销小,锁定粒度大,发生死锁概率高,相对并发也低。行锁:开销大,锁定粒度小,发生死锁概率低,相对并发也高。为什么会出现表锁和行锁呢?主要是为了保证数据的完整性,举个例子,一个用户在操作一张表,其他用户也想操作这张表,那么就要等第一个用户操作完,其他用户才能操作,表锁和行锁就是这个作用。否则多个用户同时操作一张表,肯定会数据产生冲突或者异常。根据以上看来,使用InnoDB存储引擎是最好的选择,也是MySQL5.5以后版本中默认存储引擎。每个存储引擎相关联参数比较多,以下列出主要影响数据库性能的参数。公共参数默认值:MyISAM参数默认值:InnoDB参数默认值:3.2 系统内核优化大多数MySQL都部署在linux系统上,所以操作系统的一些参数也会影响到MySQL性能,以下对linux内核进行适当优化。在linux系统中,如果进程打开的文件句柄数量超过系统默认值1024,就会提示“too many files open”信息,所以要调整打开文件句柄限制。3.3 硬件配置加大物理内存,提高文件系统性能。linux内核会从内存中分配出缓存区(系统缓存和数据缓存)来存放热数据,通过文件系统延迟写入机制,等满足条件时(如缓存区大小到达一定百分比或者执行sync命令)才会同步到磁盘。也就是说物理内存越大,分配缓存区越大,缓存数据越多。当然,服务器故障会丢失一定的缓存数据。SSD硬盘代替SAS硬盘,将RAID级别调整为RAID1+0,相对于RAID1和RAID5有更好的读写性能(IOPS),毕竟数据库的压力主要来自磁盘I/O方面。4、数据库架构扩展随着业务量越来越大,单台数据库服务器性能已无法满足业务需求,该考虑加机器了,该做集群了~~~。主要思想是分解单台数据库负载,突破磁盘I/O性能,热数据存放缓存中,降低磁盘I/O访问频率。4.1 主从复制与读写分离因为生产环境中,数据库大多都是读操作,所以部署一主多从架构,主数据库负责写操作,并做双击热备,多台从数据库做负载均衡,负责读操作,主流的负载均衡器有LVS、HAProxy、Nginx。怎么来实现读写分离呢?大多数企业是在代码层面实现读写分离,效率比较高。另一个种方式通过代理程序实现读写分离,企业中应用较少,常见代理程序有MySQL Proxy、Amoeba。在这样数据库集群架构中,大大增加数据库高并发能力,解决单台性能瓶颈问题。如果从数据库一台从库能处理2000 QPS,那么5台就能处理1w QPS,数据库横向扩展性也很容易。有时,面对大量写操作的应用时,单台写性能达不到业务需求。如果做双主,就会遇到数据库数据不一致现象,产生这个原因是在应用程序不同的用户会有可能操作两台数据库,同时的更新操作造成两台数据库数据库数据发生冲突或者不一致。在单库时MySQL利用存储引擎机制表锁和行锁来保证数据完整性,怎样在多台主库时解决这个问题呢?有一套基于perl语言开发的主从复制管理工具,叫MySQL-MMM(Master-Master replication managerfor Mysql,Mysql主主复制管理器),这个工具最大的优点是在同一时间只提供一台数据库写操作,有效保证数据一致性。主从复制博文:http://lizhenliang.blog.51cto.com/7876557/1290431读写分离博文:http://lizhenliang.blog.51cto.com/7876557/1305083MySQL-MMM博文:http://lizhenliang.blog.51cto.com/7876557/13545764.2 增加缓存给数据库增加缓存系统,把热数据缓存到内存中,如果缓存中有要请求的数据就不再去数据库中返回结果,提高读性能。缓存实现有本地缓存和分布式缓存,本地缓存是将数据缓存到本地服务器内存中或者文件中。分布式缓存可以缓存海量数据,扩展性好,主流的分布式缓存系统有memcached、redis,memcached性能稳定,数据缓存在内存中,速度很快,QPS可达8w左右。如果想数据持久化就选择用redis,性能不低于memcached。工作过程:4.3 分库分库是根据业务不同把相关的表切分到不同的数据库中,比如web、bbs、blog等库。如果业务量很大,还可将切分后的库做主从架构,进一步避免单个库压力过大。4.4 分表数据量的日剧增加,数据库中某个表有几百万条数据,导致查询和插入耗时太长,怎么能解决单表压力呢?你就该考虑是否把这个表拆分成多个小表,来减轻单个表的压力,提高处理效率,此方式称为分表。分表技术比较麻烦,要修改程序代码里的SQL语句,还要手动去创建其他表,也可以用merge存储引擎实现分表,相对简单许多。分表后,程序是对一个总表进行操作,这个总表不存放数据,只有一些分表的关系,以及更新数据的方式,总表会根据不同的查询,将压力分到不同的小表上,因此提高并发能力和磁盘I/O性能。分表分为垂直拆分和水平拆分:垂直拆分:把原来的一个很多字段的表拆分多个表,解决表的宽度问题。你可以把不常用的字段单独放到一个表中,也可以把大字段独立放一个表中,或者把关联密切的字段放一个表中。水平拆分:把原来一个表拆分成多个表,每个表的结构都一样,解决单表数据量大的问题。4.5 分区分区就是把一张表的数据根据表结构中的字段(如range、list、hash等)分成多个区块,这些区块可以在一个磁盘上,也可以在不同的磁盘上,分区后,表面上还是一张表,但数据散列在多个位置,这样一来,多块硬盘同时处理不同的请求,从而提高磁盘I/O读写性能,实现比较简单。注:增加缓存、分库、分表和分区主要由程序猿来实现。5、数据库维护数据库维护是运维工程师或者DBA主要工作,包括性能监控、性能分析、性能调优、数据库备份和恢复等。5.1 性能状态关键指标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:QPS = Questions / Uptime基于Com_commit和Com_rollback计算出TPS:TPS = (Com_commit + Com_rollback) / Uptime另一计算方式:基于Com_select、Com_insert、Com_delete、Com_update计算出QPS等待1秒再执行,获取间隔差值,第二次每个变量值减去第一次对应的变量值,就是QPSTPS计算方法:计算TPS,就不算查询操作了,计算出插入、删除、更新四个值即可。经网友对这两个计算方式的测试得出,当数据库中myisam表比较多时,使用Questions计算比较准确。当数据库中innodb表比较多时,则以Com_*计算比较准确。5.2 开启慢查询日志MySQL开启慢查询日志,分析出哪条SQL语句比较慢,使用set设置变量,重启服务失效,可以在my.cnf添加参数永久生效。分析慢查询日志,可以使用MySQL自带的mysqldumpslow工具,分析的日志较为简单。# mysqldumpslow -t 3 /var/log/mysql/mysql-slow.log #查看最慢的前三个查询也可以使用percona公司的pt-query-digest工具,日志分析功能全面,可分析slow log、binlog、general log。分析慢查询日志:pt-query-digest /var/log/mysql/mysql-slow.log分析binlog日志:mysqlbinlog mysql-bin.000001 >mysql-bin.000001.sqlpt-query-digest --type=binlog mysql-bin.000001.sql分析普通日志:pt-query-digest --type=genlog localhost.log5.3 数据库备份备份数据库是最基本的工作,也是最重要的,否则后果很严重,你懂得!但由于数据库比较大,上百G,往往备份都很耗费时间,所以就该选择一个效率高的备份策略,对于数据量大的数据库,一般都采用增量备份。常用的备份工具有mysqldump、mysqlhotcopy、xtrabackup等,mysqldump比较适用于小的数据库,因为是逻辑备份,所以备份和恢复耗时都比较长。mysqlhotcopy和xtrabackup是物理备份,备份和恢复速度快,不影响数据库服务情况下进行热拷贝,建议使用xtrabackup,支持增量备份。Xtrabackup备份工具使用博文:http://lizhenliang.blog.51cto.com/7876557/16128005.4 数据库修复有时候MySQL服务器突然断电、异常关闭,会导致表损坏,无法读取表数据。这时就可以用到MySQL自带的两个工具进行修复,myisamchk和mysqlcheck。myisamchk:只能修复myisam表,需要停止数据库常用参数:-f --force 强制修复,覆盖老的临时文件,一般不使用-r --recover 恢复模式-q --quik 快速恢复-a --analyze 分析表-o --safe-recover 老的恢复模式,如果-r无法修复,可以使用此参数试试-F --fast 只检查没有正常关闭的表快速修复weibo数据库:# cd /var/lib/mysql/weibo# myisamchk -r -q *.MYImysqlcheck:myisam和innodb表都可以用,不需要停止数据库,如修复单个表,可在数据库后面添加表名,以空格分割常用参数:-a --all-databases 检查所有的库-r --repair 修复表-c --check 检查表,默认选项-a --analyze 分析表-o --optimize 优化表-q --quik 最快检查或修复表-F --fast 只检查没有正常关闭的表快速修复weibo数据库:mysqlcheck -r -q -uroot -p123 weibo5.5 另外,查看CPU和I/O性能方法#参数-m是以M单位显示,默认K#%util:当达到100%时,说明I/O很忙。#await:请求在队列中等待时间,直接影响read时间。I/O极限:IOPS(r/s+w/s),一般RAID0/10在1200左右。(IOPS,每秒进行读写(I/O)操作次数)I/O带宽:在顺序读写模式下SAS硬盘理论值在300M/s左右,SSD硬盘理论值在600M/s左右。以上是本人使用MySQL三年来总结的一些主要优化方案,能力有限,有些不太全面,但这些基本能够满足中小型企业数据库需求。由于关系型数据库初衷设计限制,一些BAT公司海量数据放到关系型数据库中,在海量数据查询和分析方面已经达不到更好的性能。因此NoSQL火起来了,非关系型数据库,大数据量,具有高性能,同时也弥补了关系型数据库某方面不足,渐渐大多数公司已经将部分业务数据库存放到NoSQL中,如MongoDB、HBase等。数据存储方面采用分布式文件系统,如HDFS、GFS等。海量数据计算分析采用Hadoop、Spark、Storm等。这些都是与运维相关的前沿技术,也是在存储方面主要学习对象,小伙伴们共同加油吧!MySQL性能分析及explain用法的知识是本文我们主要要介绍的内容,接下来就让我们通过一些实际的例子来介绍这一过程,希望能够对您有所帮助。1.使用explain语句去查看分析结果如explain select * from test1 where id=1;会出现:id  selecttype  table  type possible_keys  key key_len  ref rows  extra各列。其中,type=const表示通过索引一次就找到了;key=primary的话,表示使用了主键;type=all,表示为全表扫描;key=null表示没用到索引。type=ref,因为这时认为是多个匹配行,在联合查询中,一般为REF。2.MYSQL中的组合索引假设表有id,key1,key2,key3,把三者形成一个组合索引,则如:where key1=....     where key1=1 and key2=2     where key1=3 and key3=3 and key2=2 根据最左原则,这些都是可以使用索引的,如from test where key1=1 order by key3,用explain分析的话,只用到了normal_key索引,但只对where子句起作用,而后面的order by需要排序。3.使用慢查询分析在my.ini中:long_query_time=1log-slow-queries=d:\mysql5\logs\mysqlslow.log把超过1秒的记录在慢查询日志中可以用mysqlsla来分析之。也可以在mysqlreport中,有如DMS分别分析了select ,update,insert,delete,replace等所占的百份比4.MYISAM和INNODB的锁定myisam中,注意是表锁来的,比如在多个UPDATE操作后,再SELECT时,会发现SELECT操作被锁定了,必须等所有UPDATE操作完毕后,再能SELECTinnodb的话则不同了,用的是行锁,不存在上面问题。5.MYSQL的事务配置项innodb_flush_log_at_trx_commit=1 表示事务提交时立即把事务日志写入磁盘,同时数据和索引也更新。innodb_flush_log_at_trx_commit=0 事务提交时,不立即把事务日志写入磁盘,每隔1秒写一次innodb_flush_log_at_trx_commit=2 事务提交时,立即写入磁盘文件(这里只是写入到内核缓冲区,但不立即刷新到磁盘,而是每隔1秒刷新到盘,同时更新数据和索引explain用法 EXPLAIN tbl_name或:EXPLAIN [EXTENDED] SELECT select_options前者可以得出一个表的字段结构等等,后者主要是给出相关的一些索引信息,而今天要讲述的重点是后者。举例mysql> explain select * from event;  +—-+————-+——-+——+—————+——+———+——+——+——-+  | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |  +—-+————-+——-+——+—————+——+———+——+——+——-+  | 1 | SIMPLE | event | ALL | NULL | NULL | NULL | NULL | 13 | |  +—-+————-+——-+——+—————+——+———+——+——+——-+  1 row in set (0.00 sec) 各个属性的含义id    select查询的序列号select_type   select查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询。table    输出的行所引用的表。type   联合查询所使用的类型。 type显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL一般来说,得保证查询至少达到range级别,最好能达到ref。possible_keys  指出MySQL能使用哪个索引在该表中找到行。如果是空的,没有相关的索引。这时要提高性能,可通过检验WHERE子句,看是否引用某些字段,或者检查字段不是适合索引。key   显示MySQL实际决定使用的键。如果没有索引被选择,键是NULL。key_len  显示MySQL决定使用的键长度。如果键是NULL,长度就是NULL。文档提示特别注意这个值可以得出一个多重主键里mysql实际使用了哪一部分。ref  显示哪个字段或常数与key一起被使用。rows  这个数表示mysql要遍历多少数据才能找到,在innodb上是不准确的。Extra  如果是Only index,这意味着信息只用索引树中的信息检索出的,这比扫描整个表要快。如果是where used,就是使用上了where限制。如果是impossible where 表示用不着where,一般就是没查出来啥。如果此信息显示Using filesort或者Using temporary的话会很吃力,WHERE和ORDER BY的索引经常无法兼顾,如果按照WHERE来确定索引,那么在ORDER BY时,就必然会引起Using filesort,这就要看是先过滤再排序划算,还是先排序再过滤划算。关于MySQL性能分析及explain用法的知识就介绍到这里了,希望本次的介绍能够对您有所收获!1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null 最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num = 0 ;3.应尽量避免在 where 子句中使用 != 或 操作符,否则将引擎放弃使用索引而进行全表扫描。4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num=10 or Name = 'admin' ;可以这样查询:select id from t where num = 10 union all select id from t where Name = 'admin' ;5.in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) ;对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3 ;很多时候用 exists 代替 in 是一个好的选择:select num from a where num in(select num from b)用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)6.下面的查询也将导致全表扫描:select id from t where name like ‘%abc%’;若要提高效率,可以考虑全文检索。7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num = @num;可以改为强制查询使用索引:select id from t with(index(索引名)) where num = @num ;8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2 = 100应改为:select id from t where num = 100*29.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where substring(name,1,3) = ’abc’       -–name以abc开头的idselect id from t where datediff(day,createdate,’2005-11-30′) = 0    -–‘2005-11-30’    --生成的id应改为:select id from t where name like 'abc%'select id from t where createdate >= '2005-11-30' and createdate10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。12.不要写一些没有意义的查询,如需要生成一个空表结构:select col1,col2 into #t from t where 1=0这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:create table #t(…)13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。29.尽量避免大事务操作,提高系统并发能力。30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。实际案例分析:拆分大的 DELETE 或INSERT 语句,批量提交SQL语句如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂了。所以,如果你有一个大的处理,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)条件是一个好的方法。
11-10 09:47
查看更多