⼤数据量时Mysql的优化
(转⾃⽹络)
如今随着互联⽹的发展,数据的量级也是撑指数的增长,从GB到TB到PB。对数据的各种操作也是愈加的困难,传统的关系性数据库已经⽆法满⾜快速查询与插⼊数据的需求。这个时候NoSQL的出现暂时解决了这⼀危机。它通过降低数据的安全性,减少对事务的⽀持,减少对复杂查询的⽀持,来获取性能上的提升。但是,在有些场合NoSQL⼀些折衷是⽆法满⾜使⽤场景的,就⽐如有些使⽤场景是绝对要有事务与安全指标的。这个时候NoSQL肯定是⽆法满⾜的,所以还是需要使⽤关系性数据库。
虽然关系型数据库在海量数据中逊⾊于NoSQL数据库,但是如果你操作正确,它的性能还是会满⾜你的需求的。针对数据的不同操作,其优化⽅向也是不尽相同。对于数据移植,查询和插⼊等操作,可以从不同的⽅向去考虑。⽽在优化的时候还需要考虑其他相关操作是否会产⽣影响。就⽐如你可以通过创建索引提⾼查询性能,但是这会导致插⼊数据的时候因为要建⽴更新索引导致插⼊性能降低,你是否可以接受这⼀降低那。所以,对数据库的优化是要考虑多个⽅向,寻⼀个折衷的最佳⽅案。
⼀:查询优化
1:创建索引。
最简单也是最常⽤的优化就是查询。因为对于CRUD操作,read操作是占据了绝⼤部分的⽐例,所以read的性能基本上决定了应⽤的性能。对于查询性能最常⽤的就是创建索引。经过测试,2000万条记录,每条记录200字节两列varchar类型的。当不使⽤索引的时候查询⼀条记录需要⼀分钟,⽽当创建了索引的时候查询时间可以忽略。但是,当你在已有数据上添加索引的时候,则需要耗费⾮常⼤的时间。我插⼊2000万条记录之后,再创建索引⼤约话费了⼏⼗分钟的样⼦。
创建索引的弊端和场合。虽然创建索引可以很⼤程度上优化查询的速度,但是弊端也是很明显的。⼀个是在插⼊数据的时候,创建索引也需要消耗部分的时间,这就使得插⼊性能在⼀定程度上降低;另⼀个很明显的是数据⽂件变的更⼤。在列上创建索引的时候,每条索引的长度是和你创建列的时候制定的长度相同的。⽐如你创建varchar(100),当你在该列上创建索引,那么索引的长度则是102字节,因为长度超过64字节则会额外增加2字节记录索引的长度。
从上图可以看到我在YCSB_KEY这⼀列(长度100)上创建了⼀个名字为index_ycsb_key的索引,每条索引长度都为102,想象⼀下当数据变的巨⼤⽆⽐的时候,索引的⼤⼩也是不可以⼩觑的。⽽且从这也可以看出,索引的长度和列类型的长度还不同,⽐如varchar它是变长的字符类型(),实际存储长度是是实际字符的⼤⼩,但是索引却是你声明的长度的⼤⼩。你创建列的时候声明100字节,那么索引长度就是这个字节再加上2,它不管你实际存储是多⼤。
除了创建索引需要消耗时间,索引⽂件体积会变的越来越⼤之外,创建索引也需要看的你存储数据的特征。当你存储数据很⼤⼀部分都是重复记录,那这个时候创建索引是百害⽽⽆⼀利。请先查看。所以,当很多数据重复的时候,索引带来的查询提升的效果是可以直接忽略的,但是这个时候你还要承受插⼊数据的时候创建索引带来的性能消耗。
2:缓存的配置。
在MySQL中有多种多样的缓存,有的缓存负责缓存查询语句,也有的负责缓存查询数据。这些缓存内容客户端⽆法操作,是由server端来维护的。它会随着你查询与修改等相应不同操作进⾏不断更新。通过其配置⽂件我们可以看到在MySQL中的缓存:
在这⾥主要分析query cache,它是主要⽤来缓存查询数据。当你想使⽤该cache,必须把query_cache_size⼤⼩设置为⾮0。当设置⼤⼩为⾮0的时
候,server会就会缓存每次查询返回的结果,到下次相同查询server就直接从缓存获取数据,⽽不是再执⾏查询。能缓存的数据量就和你的size⼤⼩设置有关,所以当你设置的⾜够⼤,数据可以完全缓存到内存,速度就会⾮常之快。
但是,query cache也有它的弊端。当你对数据表做任何的更新操作(update/insert/delete)等操作,server为了保证缓存与数据库的⼀致性,会强制刷新缓存数据,导致缓存数据全部失效。所以,当⼀个表格的更新数据表操作⾮常多的话,query cache是不会起到查询提升的性能,还会影响其他操作的性能。
3:slow_query_log分析。
其实对于查询性能提升,最重要也是最根本的⼿段也是slow_query的设置。
当你设置slow_query_log为on的时候,server端会对每次的查询进⾏记录,当超过你设置的慢查询时间 (long_query_time)的时候就把该条查询记录到⽇志。⽽你对性能进⾏优化的时候,就可以分析慢查询⽇志,对慢查询的查询语句进⾏有⽬的的优化。可以通过创建各种索引,可以通过分表等操作。那为什么要分库分表那,当不分库分表的时候那个地⽅是限制性能的地⽅啊。下⾯我们就简单介绍。
4:分库分表
分库分表应该算是查询优化的杀⼿锏了。上述各种措施在数据量达到⼀定等级之后,能起到优化的作⽤已经不明显了。这个时候就必须对数据量进⾏分流。分流⼀般有分库与分表两种措施。⽽分表⼜有垂直切分与⽔平切分两种⽅式。下⾯我们就针对每⼀种⽅式简单介绍。
查看mysql索引对于mysql,其数据⽂件是以⽂件形式存储在磁盘上的。当⼀个数据⽂件过⼤的时候,操作系统对⼤⽂件的操作就会⽐较⿇烦与耗时,⽽且有的操作系统就不⽀持⼤⽂件,所以这个时候就必须分表了。另外对于mysql常⽤的存储引擎是Innodb,它的底层数据结构是B+树。当其数据⽂件过⼤的时候,B+树就会从层次和节点上⽐较多,当查询⼀个节点的时候可能会查询很多层次,⽽这必定会导致多次IO操作进⾏装载进内存,肯定会耗时的。除此之外还有Innodb对于B+树的锁机制。对每个节点进⾏加锁,那么当更改表结构的时候,这时候就会树进⾏加锁,当表⽂件⼤的时候,这可以认为是不可实现的。
所以综上我们就必须进⾏分表与分库的操作。
5:⼦查询优化
在查询中经常会⽤到⼦查询,在⼦查询的时候⼀般使⽤in或者exist关键词。针对in和exist在查询的时候当数据量⼤到⼀定程度以后,查询执⾏时间就差别⽐较⼤。但是,为了避免此类情况出现,最好的⽅式是使⽤join查询。因为在绝⼤多数情况下,服务器对join的查询优化要远远⾼于⼦查询优化。在⽐较⾼的版本5.6,mysql查询会⾃动把in查询优化成joint查询,就不会出现⼦查询⽐较慢的问题。有时候也可以采⽤distinct关键词来限制⼦查询的数量,但是需要注意的是distinct很多时候会转化为group by,这个时候就会出现⼀个临时表,就会出现copy 数据到临时表的时延。
更多的⼦查询优化。
⼆:数据转移
当数据量达到⼀定等级之后,那么移库将是⼀个⾮常慎重⼜危险的⼯作。在移库中保证前后数据的⼀致性,各种突发情况的处理,移库过程中数据的变迁,每⼀个都是⼀个⾮常困难的问题。
2.1:插⼊数据
当进⾏数据迁移的时候,肯定会存在⼤数据的重新导⼊,你可以选择直接load⽂件,有的时候可能就需要代码插⼊了。这个时候就需要对插⼊语句进⾏⼀定的优化了。这个时候可以使⽤INSERT DELAYED
语句,该语句是当你发出插⼊请求的时候,不是马上就插⼊到数据库⽽是放在缓存⾥⾯,等待时机成熟之后再进⾏插⼊。
1、对查询进⾏优化、应尽量避免全表扫描、⾸先应考虑在 where 及 order by 涉及的列上建⽴索引。
2、应尽量避免在 where ⼦句中对字段进⾏ null 值判断、否则将导致引擎放弃使⽤索引⽽进⾏全表扫描、如:
select id from t where num is 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 num=20
-
-可以这样查询:
select id from t where num=10
union all
select id from t where num=20;
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;
6、下⾯的查询也将导致全表扫描:
select id from t where name like '%abc%';
--若要提⾼效率、可以考虑全⽂检索。
7、如果在 where ⼦句中使⽤参数、也会导致全表扫描。因为SQL只有在运⾏时才会解析局部变量、但优化程序不能将访问计划的选择推迟到运⾏时;它必须在编译时进⾏选择。然⽽、如果在编译时建⽴访问计划、变量的值还是未知的、因⽽⽆法作为索引选择的输⼊项。如下⾯语句将进⾏全表扫描:
select id from t where num= ;
--可以改为强制查询使⽤索引:
select id from t with(index(索引名)) where num= ;
8、应尽量避免在 where ⼦句中对字段进⾏表达式操作、这将导致引擎放弃使⽤索引⽽进⾏全表扫描。如:
select id from t where num/2=100;
--应改为:
select id from t where num=100*2;
9、应尽量避免在where⼦句中对字段进⾏函数操作、这将导致引擎放弃使⽤索引⽽进⾏全表扫描。如:
select id from t where substring(name,1,3)='abc';
--name以abc开头的id
select 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 createdate<'2005-12-1';
10、不要在 where ⼦句中的“=”左边进⾏函数、算术运算或其他表达式运算、否则系统将可能⽆法正确使⽤索引。
11、在使⽤索引字段作为条件时、如果该索引是复合索引、那么必须使⽤到该索引中的第⼀个字段作为条件时才能保证系统使⽤该索引、否则该索引将不会被使⽤、并且应尽可能的让字段顺序与索引顺序相⼀致。
12、不要写⼀些没有意义的查询、如需要⽣成⼀个空表结构:
select col1,col2 into #t from t where 1=0;
--这类代码不会返回任何结果集、但是会消耗系统资源的、应改成这样:
create table #t(...);
13、很多时候⽤ 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);
14、并不是所有索引对查询都有效、SQL是根据表中数据来进⾏查询优化的、当索引列有⼤量数据重复时、SQL查询可能不会去利⽤索引、如⼀表中有字段sex、male、female⼏乎各⼀半、那么即使在sex上建了索引也对查询效率起不了作⽤。
15、索引并不是越多越好、索引固然可以提⾼相应的 select 的效率、但同时也降低了 insert 及 update
的效率、因为 insert 或 update 时有可能会重建索引、所以怎样建索引需要慎重考虑、视具体情况⽽定。⼀个表的索引数最好不要超过6个、若太多则应考虑⼀些不常使⽤到的列上建的索引是否有必要。
16、应尽可能的避免更新 clustered 索引数据列、因为 clustered 索引数据列的顺序就是表记录的物理存储顺序、⼀旦该列值改变将导致整个表记录的顺序的调整、会耗费相当⼤的资源。若应⽤系统需要频繁更新 clustered 索引数据列、那么需要考虑是否应将该索引建为 clustered 索引。
17、尽量使⽤数字型字段、若只含数值信息的字段尽量不要设计为字符型、这会降低查询和连接的性能、并会增加存储开销。这是因为引擎在处理查询和连接时会逐个⽐较字符串中每⼀个字符、⽽对于数字型⽽⾔只需要⽐较⼀次就够了。
18、尽可能的使⽤ varchar/nvarchar 代替 char/nchar 、因为⾸先变长字段存储空间⼩、可以节省存储空间、其次对于查询来说、在⼀个相对较⼩的字段内搜索效率显然要⾼些。
19、任何地⽅都不要使⽤ select * from t 、⽤具体的字段列表代替“*”、不要返回⽤不到的任何字段。
20、尽量使⽤表变量来代替临时表。如果表变量包含⼤量数据、请注意索引⾮常有限(只有主键索引)。
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、尽量避免向客户端返回⼤数据量、若数据量过⼤、应该考虑相应需求是否合理。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论