MySQL处理达到百万级数据时,如何优化?

1.两种查询引擎查询速度(myIsam 引擎	)
	InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行。
	MyISAM只要简单的读出保存好的行数便可。
	注意的是,当count(*)语句包含 where条件时,两种表的操做有些不一样,InnoDB类型的表用count(*)或者count(主键),加上where col 条件。其中col列是表的主键以外的其余具备惟一约束索引的列。这样查询时速度会很快。就是能够避免全表扫描。
	总结:
		mysql 在300万条数据(myisam引擎)状况下使用 count(*) 进行数据总数查询包含条件(正确设置索引)运行时间正常。对于常常进行读取的数据咱们建议使用myIsam引擎。
2.百万数据下mysql分页问题
	在开发过程当中咱们常常会使用分页,核心技术是使用limit进行数据的读取,在使用limit进行分页的测试过程当中,获得如下数据:
		select * from news order by id desc limit 0,10
		耗时0.003秒
		select * from news order by id desc limit 10000,10
		耗时0.058秒
		select * from news order by id desc limit 100000,10 
		耗时0.575秒
		select * from news order by id desc limit 1000000,10
		耗时7.28秒
	咱们惊讶的发现mysql在数据量大的状况下分页起点越大查询速度越慢,100万条起的查询速度已经须要7秒钟。这是一个咱们没法接受的数值!
	改进方案 1
		select * from news 
		where id >  (select id from news order by id desc  limit 1000000, 1)
		order by id desc 
		limit 0,10
	查询时间 0.365秒,提高效率是很是明显的!!原理是什么呢???
	咱们使用条件对id进行了筛选,在子查询 (select id from news order by id desc limit 1000000, 1) 中咱们只查询了id这一个字段比起select * 或 select 多个字段 节省了大量的查询开销!

	改进方案2
	适合id连续的系统,速度极快!
		select * from news 
		where id  between 1000000 and 1000010 
		order by id desc
	不适合带有条件的、id不连续的查询。速度很是快!
3.	百万数据下mysql条件查询、分页查询的注意事项
	接上一节,咱们加上查询条件:
		select id from news 
		where cate = 1
		order by id desc 
		limit 500000 ,10 
		查询时间 20 秒
	好恐怖的速度!!利用第一节知识进行优化:
		select * from news
		where cate = 1 and id > (select id from news where cate = 1 order by id desc limit 500000,1 ) 
		order by id desc 
		limit 0,10 
		查询时间 15 秒
	优化效果不明显,条件带来的影响仍是很大!在这样的状况下不管咱们怎么去优化sql语句就没法解决运行效率问题。那么换个思路:创建一个索引表,只记录文章的id、分类信息,咱们将文章内容这个大字段分割出去。
		表 news2 [ 文章表 引擎 myisam 字符集 utf-8 ]
		-------------------------------------------------
		id	int	11	主键自动增长
		cate	int	11	索引
	在写入数据时将2张表同步,查询是则可使用news2 来进行条件查询:
		select * from news
		where cate = 1 and id > (select id from news2 where cate = 1 order by id desc limit 500000,1 ) 
		order by id desc 
		limit 0,10
	注意条件 id > 后面使用了news2 这张表!
	运行时间 1.23秒,咱们能够看到运行时间缩减了近20倍!!数据在10万左右是查询时间能够保持在0.5秒左右,是一个逐步接近咱们可以容忍的值!
	可是1秒对于服务器来讲依然是一个不能接受的值!!还有什么能够优化的办法吗??咱们尝试了一个伟大的变化:
	将 news2 的存储引擎改变为innodb,执行结果是惊人的!
		select * from news
		where cate = 1 and id > (select id from news2 where cate = 1 order by id desc limit 500000,1 ) 
		order by id desc 
		limit 0,10
	只须要 0.2秒,很是棒的速度。为何会有怎么大的差异呢?请观看下一章 mysql存储引擎详解。
4.mysql存储引擎 myIsam和innodb的区别	
	MySQL有多种存储引擎,MyISAM和InnoDB是其中经常使用的两种。这里介绍关于这两种引擎的一些基本概念(非深刻介绍)。
		MyISAM是MySQL的默认存储引擎,基于传统的ISAM类型,支持全文搜索,但不是事务安全的,并且不支持外键。每张MyISAM表存放在三个文件中:frm 文件存放表格定义;数据文件是MYD (MYData);索引文件是MYI (MYIndex)。
		InnoDB是事务型引擎,支持回滚、崩溃恢复能力、多版本并发控制、ACID事务,支持行级锁定(InnoDB表的行锁不是绝对的,若是在执行一个SQL语句时MySQL不能肯定要扫描的范围,InnoDB表一样会锁全表,如like操做时的SQL语句),以及提供与Oracle类型一致的不加锁读取方式。InnoDB存储它的表和索引在一个表空间中,表空间能够包含数个文件。
	核心区别
		MyISAM是非事务安全型的,而InnoDB是事务安全型的。
		MyISAM锁的粒度是表级,而InnoDB支持行级锁定。
		MyISAM支持全文类型索引,而InnoDB不支持全文索引。
		MyISAM相对简单,因此在效率上要优于InnoDB,小型应用能够考虑使用MyISAM。
		MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去很多的麻烦。
		InnoDB表比MyISAM表更安全,能够在保证数据不会丢失的状况下,切换非事务表到事务表(alter table tablename type=innodb)。	
	应用场景
		MyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。若是应用中须要执行大量的SELECT查询,那么MyISAM是更好的选择。
		InnoDB用于事务处理应用程序,具备众多特性,包括ACID事务支持。若是应用中须要执行大量的INSERT或UPDATE操做,则应该使用InnoDB,这样能够提升多用户并发操做的性能。		
	Mysql的存储引擎和索引
		数据库必须有索引,没有索引则检索过程变成了顺序查找,O(n)的时间复杂度几乎是不能忍受的。咱们很是容易想象出一个只有单关键字组成的表如何使用B+树进行索引,只要将关键字存储到树的节点便可。当数据库一条记录里包含多个字段时,一棵B+树就只能存储主键,若是检索的是非主键字段,则主键索引失去做用,又变成顺序查找了。这时应该在第二个要检索的列上创建第二套索引。 这个索引由独立的B+树来组织。有两种常见的方法能够解决多个B+树访问同一套表数据的问题,一种叫作聚簇索引(clustered index ),一种叫作非聚簇索引(secondary index)。这两个名字虽然都叫作索引,但这并非一种单独的索引类型,而是一种数据存储方式。对于聚簇索引存储来讲,行数据和主键B+树存储在一块儿,辅助键B+树只存储辅助键和主键,主键和非主键B+树几乎是两种类型的树。对于非聚簇索引存储来讲,主键B+树在叶子节点存储指向真正数据行的指针,而非主键。		
		InnoDB使用的是聚簇索引,将主键组织到一棵B+树中,而行数据就储存在叶子节点上,若使用"where id = 14"这样的条件查找主键,则按照B+树的检索算法便可查找到对应的叶节点,以后得到行数据。若对Name列进行条件搜索,则须要两个步骤:第一步在辅助索引B+树中检索Name,到达其叶子节点获取对应的主键。第二步使用主键在主索引B+树种再执行一次B+树检索操做,最终到达叶子节点便可获取整行数据。
		MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不一样,节点的结构彻底一致只是存储的内容不一样而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来讲,这两个键没有任何差异。因为索引树是独立的,经过辅助键检索无需访问主键的索引树。
		为了更形象说明这两种索引的区别,咱们假想一个表以下图存储了4行数据。其中Id做为主索引,Name做为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差别。
		
		咱们重点关注聚簇索引,看上去聚簇索引的效率明显要低于非聚簇索引,由于每次使用辅助索引检索都要通过两次B+树查找,这不是画蛇添足吗?聚簇索引的优点在哪?
	
咱们重点关注聚簇索引,看上去聚簇索引的效率明显要低于非聚簇索引,由于每次使用辅助索引检索都要通过两次B+树查找,这不是画蛇添足吗?聚簇索引的优点在哪?
			1 因为行数据和叶子节点存储在一块儿,这样主键和行数据是一块儿被载入内存的,找到叶子节点就能够马上将行数据返回了,若是按照主键Id来组织数据,得到数据更快。
			2 辅助索引使用主键做为"指针" 而不是使用地址值做为指针的好处是,减小了当出现行移动或者数据页分裂时辅助索引的维护工做,使用主键值看成指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针"。也就是说行的位置(实现中经过16K的Page来定位,后面会涉及)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就能够保证无论这个主键B+树的节点如何变化,辅助索引树都不受影响。
		因此在百万级数据及更大数据状况下,mysql innoDB 的索引表现更加优秀!
五、MySQL性能优化的一些经验
	a.为查询优化你的查询
		大多数的MySQL服务器都开启了查询缓存。这是提升性能最有效的方法之一,并且这是被MySQL的数据库引擎处理的。当有不少相同的查询被执行了屡次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操做表而直接访问缓存结果了。
		这里最主要的问题是,对于程序员来讲,这个事情是很容易被忽略的。由于,咱们某些查询语句会让MySQL不使用缓存。
		请看下面的示例:
			// 查询缓存不开启
			$r = mysql_query("SELECT username FROM user WHERE     signup_date >= CURDATE()");
			// 开启查询缓存
			$today = date("Y-m-d");
			$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
		上面两条SQL语句的差异就是 CURDATE() ,MySQL的查询缓存对这个函数不起做用。因此,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,由于这些函数的返回是会不定的易变的。因此,你所须要的就是用一个变量来代替MySQL的函数,从而开启缓存。
	b.学会使用EXPLAIN
		使用EXPLAIN关键字可让你知道MySQL是如何处理你的SQL语句的。
			select id, title, cate from news where cate = 1
		发现查询缓慢,而后在cate字段上增长索引,则会加快查询
	c.当只要一行数据时使用LIMIT 1
		当你查询表的有些时候只须要一条数据,请使用 limit 1。
	d.正确的使用索引
		索引并不必定就是给主键或是惟一的字段。若是在你的表中,有某个字段你总要会常常用来作搜索、拍下、条件,那么,请为其创建索引吧。
	e.不要ORDER BY RAND()
		效率很低的一种随机查询。
	f.避免SELECT *
		从数据库里读出越多的数据,那么查询就会变得越慢。而且,若是你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增长网络传输的负载。必须应该养成一个须要什么就取什么的好的习惯。
	g.使用 ENUM 而不是 VARCHAR
		ENUM 类型是很是快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来作一些选项列表变得至关的完美。
		若是你有一个字段,好比“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限并且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。
	h.使用 NOT NULL
		除非你有一个很特别的缘由去使用 NULL 值,你应该老是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。
		首先,问问你本身“Empty”和“NULL”有多大的区别(若是是INT,那就是0和NULL)?若是你以为它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是同样的!)
		不要觉得 NULL 不须要空间,其须要额外的空间,而且,在你进行比较的时候,你的程序会更复杂。 固然,这里并非说你就不能使用NULL了,现实状况是很复杂的,依然会有些状况下,你须要使用NULL值。
		
		下面摘自MySQL本身的文档
			“NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”
	i.IP地址存成 UNSIGNED INT
		不少程序员都会建立一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。若是你用整形来存放,只须要4个字节,而且你能够有定长的字段。并且,这会为你带来查询上的优点,尤为是当你须要使用这样的WHERE条件:IP between ip1 and ip2。
		咱们必须要使用UNSIGNED INT,由于 IP地址会使用整个32位的无符号整形
	j.固定长度的表会更快
		若是表中的全部字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有以下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另外一种方法来处理。
		固定长度的表会提升性能,由于MySQL搜寻得会更快一些,由于这些固定的长度是很容易计算下一个数据的偏移量的,因此读取的天然也会很快。而若是字段不是定长的,那么,每一次要找下一条的话,须要程序找到主键。
		而且,固定长度的表也更容易被缓存和重建。不过,惟一的反作用是,固定长度的字段会浪费一些空间,由于定长的字段不管你用不用,他都是要分配那么多的空间。
	k.垂直分割
		“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样能够下降表的复杂度和字段的数目,从而达到优化的目的。须要注意的是,这些被分出去的字段所造成的表,你不会常常性地去Join他们,否则的话,这样的性能会比不分割时还要差,并且,会是极数级的降低。
	l.拆分大的 DELETE 或 INSERT 语句
		若是在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你须要很是当心,要避免你的操做让你的整个网站中止相应。由于这两个操做是会锁表的,表一锁住了,别的操做都进不来了。
		Apache 会有不少的子进程或线程。因此,其工做起来至关有效率,而咱们的服务器也不但愿有太多的子进程,线程和数据库连接,这是极大的占服务器资源的事情,尤为是内存。
		若是你把你的表锁上一段时间,好比30秒钟,那么对于一个有很高访问量的站点来讲,这30秒所积累的访问进程/线程,数据库连接,打开的文件数,可能不只仅会让你泊WEB服务Crash,还可能会让你的整台服务器立刻掛了。
	m.越小的列会越快
		对于大多数的数据库引擎来讲,硬盘操做多是最重大的瓶颈。因此,把你的数据变得紧凑会对这种状况很是有帮助,由于这减小了对硬盘的访问。
	n.选择正确的存储引擎
		在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每一个引擎都有利有弊。
		MyISAM 适合于一些须要大量查询的应用,但其对于有大量写操做并非很好。甚至你只是须要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都没法操做直到读操做完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
		InnoDB 的趋势会是一个很是复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,因而在写操做比较多的时候,会更优秀。而且,他还支持更多的高级应用,好比:事务。

 

 

经测试对一个包含400多万条记录的表执行一条件查询,其查询时间居然高达40几秒,相信这么高的查询延时,任何用户都会抓狂。所以如何提升sql语句查询效率,显得十分重要。如下是结合网上流传比较普遍的几个查询语句优化方法:mysql

    首先,数据量大的时候,应尽可能避免全表扫描,应考虑在 where及 order by 涉及的列上创建索引,建索引能够大大加快数据的检索速度。可是,有些状况索引是不会起效的:程序员

一、应尽可能避免在 where子句中使用!=或<>操做符,不然将引擎放弃使用索引而进行全表扫描。redis

二、应尽可能避免在 where子句中对字段进行 null值判断,不然将致使引擎放弃使用索引而进行全表扫描,如:
     select id from t where num is null
     能够在num上设置默认值0,确保表中num列没有null值,而后这样查询:
     select id from t where num=0算法

三、尽可能避免在 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=20sql

四、下面的查询也将致使全表扫描:数据库

    select id from t where name like ‘%abc%’缓存

    若要提升效率,能够考虑全文检索。安全

五、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性能优化

六、若是在 where子句中使用参数,也会致使全表扫描。由于SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,若是在编译时创建访问计划,变量的值仍是未知的,于是没法做为索引选择的输入项。以下面语句将进行全表扫描:
     select id from t where num=@num
     能够改成强制查询使用索引:
     select id from t with(index(索引名)) where num=@num服务器

七、应尽可能避免在 where子句中对字段进行表达式操做,这将致使引擎放弃使用索引而进行全表扫描。如:
     select id from t where num/2=100
     应改成:
     select id from t where num=100*2

八、应尽可能避免在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′

九、不要在 where子句中的“=”左边进行函数、算术运算或其余表达式运算,不然系统将可能没法正确使用索引。

十、在使用索引字段做为条件时,若是该索引是复合索引,那么必须使用到该索引中的第一个字段做为条件时才能保证系统使用该索引,不然该索引将不会被使用,而且应尽量的让字段顺序与索引顺序相一致。

十一、不要写一些没有意义的查询,如须要生成一个空表结构:
     select col1,col2 into #t from t where 1=0
     这类代码不会返回任何结果集,可是会消耗系统资源的,应改为这样:
     create table #t(…)

十二、不少时候用 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)

 

    建索引须要注意的地方:

一、并非全部索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段 sex,male、female几乎各一半,那么即便在sex上建了索引也对查询效率起不了做用。

二、索引并非越多越好,索引当然能够提升相应的 select的效率,但同时也下降了 insert及 update的效率,由于 insert或 update时有可能会重建索引,因此怎样建索引须要慎重考虑,视具体状况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

三、应尽量的避免更新 clustered索引数据列,由于 clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将致使整个表记录的顺序的调整,会耗费至关大的资源。若应用系统须要频繁更新 clustered索引数据列,那么须要考虑是否应将该索引建为 clustered索引。

 

    其余须要注意的地方:

一、尽可能使用数字型字段,若只含数值信息的字段尽可能不要设计为字符型,这会下降查询和链接的性能,并会增长存储开销。这是由于引擎在处理查询和链接时会逐个比较字符串中每个字符,而对于数字型而言只须要比较一次就够了。

二、任何地方都不要使用 select * from t,用具体的字段列表代替“*”,不要返回用不到的任何字段。

三、尽可能使用表变量来代替临时表。若是表变量包含大量数据,请注意索引很是有限(只有主键索引)。

四、避免频繁建立和删除临时表,以减小系统表资源的消耗。

五、临时表并非不可以使用,适当地使用它们可使某些例程更有效,例如,当须要重复引用大型表或经常使用表中的某个数据集时。可是,对于一次性事件,最好使用导出表。

六、在新建临时表时,若是一次性插入数据量很大,那么可使用 select into代替 create table,避免形成大量 log,以提升速度;若是数据量不大,为了缓和系统表的资源,应先create table,而后insert。

七、若是使用到了临时表,在存储过程的最后务必将全部的临时表显式删除,先 truncate table,而后 drop table,这样能够避免系统表的较长时间锁定。

八、尽可能避免使用游标,由于游标的效率较差,若是游标操做的数据超过1万行,那么就应该考虑改写。

九、使用基于游标的方法或临时表方法以前,应先寻找基于集的解决方案来解决问题,基于集的方法一般更有效。

十、与临时表同样,游标并非不可以使用。对小型数据集使用 FAST_FORWARD 游标一般要优于其余逐行处理方法,尤为是在必须引用几个表才能得到所需的数据时。在结果集中包括“合计”的例程一般要比使用游标执行的速度快。若是开发时间容许,基于游标的方法和基于集的方法均可以尝试一下,看哪种方法的效果更好。

十一、在全部的存储过程和触发器的开始处设置 SET NOCOUNT ON,在结束时设置 SET NOCOUNT OFF。无需在执行存储过程和触发器的每一个语句后向客户端发送 DONE_IN_PROC消息。

十二、尽可能避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

1三、尽可能避免大事务操做,提升系统并发能力。

 

总结

# 第一优化你的sql和索引;
# 第二加缓存,memcached,redis;
# 第三以上都作了后,仍是慢,就作主从复制或主主复制,读写分离,
    能够在应用层作,效率高,也能够用三方工具,第三方工具推荐360的atlas,其它的要么效率不高,要么没人维护;
# 第四若是以上都作了仍是慢,不要想着去作切分,
    mysql自带分区表,先试试这个,对你的应用是透明的,无需更改代码,可是sql语句是须要针对分区表作优化的,sql条件中要带上分区条件的列,从而使查询定位到少许的分区上,
不然就会扫描所有分区,另外分区表还有一些坑,在这里就很少说了;
# 第五若是以上都作了,那就先作垂直拆分, 其实就是根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统; # 第六才是水平切分, 针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,作必定的冗余,应用也要改,sql中尽可能带sharding key,
将数据定位到限定的表上去查,而不是扫描所有的表;
# mysql数据库通常都是按照这个步骤去演化的,成本也是由低到高。

1

相关文章
相关标签/搜索