美文网首页
mysql优化学习

mysql优化学习

作者: Aimerwhy | 来源:发表于2017-12-04 23:13 被阅读0次

    1.高并发优化点有:

    如果请求过多,判定web服务器的压力过大,增加前端的web服务器,做负载均衡

    如果请求静态界面不卡了,但是动态数据还是卡,说明MySQL处理的请求太多了,在应用层增加缓存.

    数据库层其实是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求,所以,我们通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。但是如果请求激增,还是有大量的查询压力到MySQL,这个时候就要想办法解决MySQL的瓶颈了。

    2.mysql执行流程:

    大致可以分为以下步骤:

    1.当我们请求mysql服务器的时候,MySQL前端会有一个监听,请求到了之后,服务器得到相关的SQL语句,执行之前(虚线部分为执行),还会做权限的判断

    2.通过权限之后,SQL就到MySQL内部,他会在查询缓存中,看该SQL有没有执行过,如果有查询过,则把缓存结果返回,说明在MySQL内部,也有一个查询缓存.但是这个查询缓存,默认是不开启的,这个查询缓存,和我们的Hibernate,Mybatis的查询缓存是一样的,因为查询缓存要求SQL和参数都要一样,所以这个命中率是非常低的。

    3.如果我们没有开启查询缓存,或者缓存中没有找到对应的结果,那么就到了解析器,解析器主要对SQL语法进行解析

    4.解析结束后就变成一颗解析树,这个解析树其实在Hibernate里面也是有的,大家回忆一下,在以前做过Hibernate项目的时候,是不是有个一个antlr.jar。这个就是专门做语法解析的工具.因为在Hibernate里面有HQL,它就是通过这个工具转换成SQL的,我们编程语言之所以有很多规范、语法,其实就是为了便于这个解析器解析,这个学过编译原理的应该知道.

    5.得到解析树之后,不能马上执行,这还需要对这棵树进行预处理,也就是说,这棵树,我没有经过任何优化的树,预处理器会这这棵树进行一些预处理,比如常量放在什么地方,如果有计算的东西,把计算的结果算出来等等...

    6.预处理完毕之后,此时得到一棵比较规范的树,这棵树就是要拿去马上做执行的树,比起之前的那棵树,这棵得到了一些优化的解析树

    7.查询优化器,是MySQL里面最关键的东西,我们写任何一条SQL,比如SELECT * FROM USER WHERE USERNAME = toby AND PASSWORD = 1,它会怎么去执行?它是先执行username = toby还是password = 1?每一条SQL的执行顺序查询优化器就是根据MySQL对数据统计表的一些信息,比如索引,比如表一共有多少数据,MySQL都是有缓存起来的,在真正执行SQL之前,他会根据自己的这些数据,进行一个综合的判定,判断这一次在多种执行方式里面,到底选哪一种执行方式,可能运行的最快.这一步是MySQL性能中,最关键的核心点,也是我们的优化原则.我们平时所讲的优化SQL,其实说白了,就是想让查询优化器,按照我们的想法,帮我们选择最优的执行方案,因为我们比MySQL更懂我们的数据.MySQL看数据,仅仅只是自己收集到的信息,这些信息可能是不准确的,MySQL根据这些信息选了一个它自认为最优的方案,但是这个方案可能和我们想象的不一样.

    8.这里的查询执行计划,也就是MySQL查询中的执行计划,比如要先执行username = toby还是password = 1

    9.这个执行计划会传给查询执行引擎,执行引擎选择存储引擎来执行这一份传过来的计划,到磁盘中的文件中去查询,这个时候重点来了,影响这个查询性能最根本的原因是什么?就是硬盘的机械运动,也就是我们平时熟悉的IO,所以一条查询语句是快还是慢,就是根据这个时间的IO来确定的.那怎么执行IO又是什么来确定的?就是传过来的这一份执行计划.

    10.如果开了查询缓存,则返回结果给客户端,并且查询缓存也放一份。


    #要提高MySQL的更新/插入效率,应首先考虑降低锁的竞争,减少写操作的等待时间

    3.增删改查

    一、INSERT语句:

    基本:INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), …]

    注意:

    如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。

    可同时插入多条数据记录!

    REPLACE 与 INSERT 完全一样,可互换。

    优化前例子:

    优化策略:

    (1)当我们需要批量插入数据的时候,这样的语句却会出现性能问题。例如说,如果有需要插入100000条数据,那么就需要有100000条insert语句,每一句都需要提交到关系引擎那里去解析,优化,然后才能够到达存储引擎做真的插入工作。上述所说的同时插入多条就是一种优化。(经测试,大概10条同时插入是最高效的)

    优化后例子:

    (2)将进程/线程数控制在2倍于CPU数目相对合适

    (3)采用顺序主键策略(例如自增主键,或者修改业务逻辑,让插入的记录尽可能顺序主键)

    (4)考虑使用replace 语句代替insert语句。(REPLACE语句请参考下文,有详细讲述)

    二、DELETE语句:

    DELETE FROM 表名[ 删除条件子句](没有条件子句,则会删除全部)

    例子:

    补充:Mysql中的truncate table和delete语句都可以删除表里面所有数据,但是在一些情况下有些不同!

    例子:

    truncate table gag;

    (1)truncate table删除速度更快,,但truncate table删除后不记录mysql日志,不可以恢复数据。(谨慎使用)

    (2)如果没有外键关联,innodb执行truncate是先drop table(原始表),再创建一个跟原始表一样空表,速度要远远快于delete逐条删除行记录。

    (3)如果使用innodb_file_per_table参数,truncate table 能重新利用释放的硬盘空间,在InnoDB Plugin中,truncate table为自动回收,如果不是用InnoDB Plugin,那么需要使用optimize table来优化表,释放空间。

    truncate table删除表后,optimize table尤其重要,特别是大数据数据库,表空间可以得到释放!

    (4)表有外键关联,truncate table删除表数据为逐行删除,如果外键指定级联删除(delete cascade),关联的子表也会会被删除所有表数据。如果外键未指定级联(cascde),truncate table逐行删除数据,如果是父行关联子表行数据,将会报错。

    注意:

    一个大的 DELETE 或 INSERT 操作,要非常小心,因为这两个操作是会锁表的,表一锁住,其他操作就进不来了。因此,我们要交给DBA去拆分,重整数据库策略,比如限制处理1000条。

    另外,扩展下删除和索引的联系,由于索引需要额外的维护成本;因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟),然后删除其中无用数据,此过程需要不到两分钟,删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。

    三、UPDATE语句:

    UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件]

    例子:

    优化:更新多条记录(往后会结合MyBatics写个实例)

    更新多条记录的多个值

    (1). 尽量不要修改主键字段。(废话,反正我就从没改过..)

    (2). 当修改VARCHAR型字段时,尽量使用相同长度内容的值代替。

    (3). 尽量最小化对于含有UPDATE触发器的表的UPDATE操作。

    (4). 避免UPDATE将要复制到其他数据库的列。

    (5). 避免UPDATE建有很多索引的列。

    (6). 避免UPDATE在WHERE子句条件中的列。

    四、REPLACE语句:

    根据应用情况可以使用replace 语句代替insert/update语句。例如:如果一个表在一个字段上建立了唯一索引,当向这个表中使用已经存在的键值插入一条记录,将会抛出一个主键冲突的错误。如果我们想用新记录的值来覆盖原来的记录值时,就可以使用REPLACE语句。

    使用REPLACE插入记录时,如果记录不重复(或往表里插新记录),REPLACE功能与INSERT一样,如果存在重复记录,REPLACE就使用新记录的值来替换原来的记录值。使用REPLACE的最大好处就是可以将DELETE和INSERT合二为一,形成一个原子操作。这样就可以不必考虑同时使用DELETE和INSERT时添加事务等复杂操作了。

    在使用REPLACE时,表中必须有唯一有一个PRIMARY KEY或UNIQUE索引,否则,使用一个REPLACE语句没有意义。

    用法:

    (1)同INSERT

    含义一:与普通INSERT一样功能

    REPLACE INTO score (change_type,score,user_id) VALUES ('吃饭',10,1),('喝茶',10,1),('喝茶',10,1);

    含义二:找到第一条记录,用后面的值进行替换

    REPLACE INTO score (id,change_type,score,user_id) VALUES (1,'吃饭',10,1)

    此语句的作用是向表table中插入3条记录。如果主键id为1或2不存在就相当于插入语句:

    INSERT INTO score (change_type,score,user_id) VALUES (‘吃饭’,10,1),(‘喝茶’,10,1),(‘喝茶’,10,1);

    如果存在相同的值则不会插入数据。

    (2)replace(object, search, replace),把object中出现search的全部替换为replace。

    用法一:并不是修改数据,而只是单纯做局部替换数据返还而已。

    SELECT REPLACE('喝茶','茶','喝')//结果: 喝喝123

    用法二:修改表数据啦,对应下面就是,根据change_type字段找到做任务的数据,用bb来替换

    UPDATE score SET change_type=REPLACE(change_type,'做任务','bb')1

    在此,做下对比:UPDATE和REPLACE的区别:

    1)UPDATE在没有匹配记录时什么都不做,而REPLACE在有重复记录时更新,在没有重复记录时插入。

    2)UPDATE可以选择性地更新记录的一部分字段。而REPLACE在发现有重复记录时就将这条记录彻底删除,再插入新的记录。也就是说,将所有的字段都更新了。

    其实REPLACE更像INSERT与DELETE的结合。

    单表查询优化

    (0)可以先使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮我们分析是查询语句或是表结构的性能瓶颈。

    (1)写sql要明确需要的字段,要多少就写多少字段,而不是滥用 select *

    (2)可以用使用连接(JOIN)来代替子查询

    (3)使用分页语句:limit start , count 或者条件 where子句时,有什么可限制的条件尽量加上,查一条就limit一条。做到不滥用。比如说我之前做过的的p2p项目,只是需要知道有没有一个满标的借款,这样的话就可以用上 limit 1,这样mysql在找到一条数据后就停止搜索,而不是全文搜索完再停止。

    (4)开启查询缓存:

    大多数的MySQL服务器都开启了查询缓存。这是提高查询有效的方法之一。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。

    查询缓存工作流程:

    A):服务器接收SQL,以SQL+DB+Query_cache_query_flags作为hash查找键;

    B):找到了相关的结果集就将其返回给客户端;

    C):如果没有找到缓存则执行权限验证、SQL解析、SQL优化等一些列的操作;

    D):执行完SQL之后,将结果集保存到缓存

    当然,并不是每种情况都适合使用缓存,衡量打开缓存是否对系统有性能提升是一个整体的概念。那怎么判断要不要开启缓存呢,如下:

    1)通过缓存命中率判断, 缓存命中率 = 缓存命中次数 (Qcache_hits) / 查询次数 (Com_select)、

    2)通过缓存写入率, 写入率 = 缓存写入次数 (Qcache_inserts) / 查询次数 (Qcache_inserts)

    3)通过 命中-写入率 判断, 比率 = 命中次数 (Qcache_hits) / 写入次数 (Qcache_inserts), 高性能MySQL中称之为比较能反映性能提升的指数,一般来说达到3:1则算是查询缓存有效,而最好能够达到10:1

    相关参数及命令:

    与缓存相关的主要参数如下表所示。可以使用命令SHOW VARIABLES LIKE '%query_cache%'查看

    缓存数据失效时机

    在表的结构或数据发生改变时,查询缓存中的数据不再有效。有这些INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE会导致缓存数据失效。所以查询缓存适合有大量相同查询的应用,不适合有大量数据更新的应用。

    可以使用下面三个SQL来清理查询缓存:

    1、FLUSH QUERY CACHE; // 清理查询缓存内存碎片。

    2、RESET QUERY CACHE; // 从查询缓存中移出所有查询。

    3、FLUSH TABLES; //关闭所有打开的表,同时该操作将会清空查询缓存中的内容。

    InnoDB与查询缓存:

    Innodb会对每个表设置一个事务计数器,里面存储当前最大的事务ID.当一个事务提交时,InnoDB会使用MVCC中系统事务ID最大的事务ID跟新当前表的计数器.

    只有比这个最大ID大的事务能使用查询缓存,其他比这个ID小的事务则不能使用查询缓存.

    另外,在InnoDB中,所有有加锁操作的事务都不使用任何查询缓存

    (MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能)

    多表查询连接的选择:

    相信这内连接,左连接什么的大家都比较熟悉了,当然还有左外连接什么的,基本用不上我就不贴出来了。这图只是让大家回忆一下,各种连接查询。 然后要告诉大家的是,需要根据查询的情况,想好使用哪种连接方式效率更高。

    二、MySQL的JOIN实现原理

    在MySQL 中,只有一种Join 算法,就是大名鼎鼎的Nested Loop Join,他没有其他很多数据库所提供的Hash Join,也没有Sort Merge Join。顾名思义,Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果还有第三个参与Join,则再通过前两个表的Join 结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复。 ——摘自《MySQL 性能调优与架构设计》

    三、补充:mysql对sql语句的容错问题

    即在sql语句不完全符合书写建议的情况,mysql会允许这种情况,尽可能解释它:

    1)一般cross join后面加上where条件,但是用cross join+on也是被解释为cross join+where

    2)一般内连接都需要加上on限定条件,如上面场景一;如果不加会被解释为交叉连接;

    3)如果连接表格使用的是逗号,会被解释为交叉连接;

    超大型数据尽可能尽力不要写子查询,使用连接(JOIN)去替换它:

    当然,关于这句话,也不一定就全是这样。

    1)因为在大型的数据处理中,子查询是非常常见的,特别是在查询出来的数据需要进一步处理的情况,无论是可读性还是效率上,这时候的子查都是更优。

    2)然而在一些特定的场景,可以直接从数据库读取就可以的,比如一个表(A表 a,b,c字段,需要内部数据交集)join自己的效率必然比放一个子查在where中快得多。

    使用联合(UNION)来代替手动创建的临时表

    UNION是会把结果排序的!!!

    union查询:它可以把需要使用临时表的两条或更多的select查询合并的一个查询中(即把两次或多次查询结果合并起来。)。在客户端的查询会话结束的时候,临时表会被自动删除,从而保证数据库整齐、高效。使用union来创建查询的时候,我们只需要用UNION作为关键字把多个select语句连接起来就可以了,要注意的是所有select语句中的字段数目要想同。

    要求:两次查询的列数必须一致(列的类型可以不一样,但推荐查询的每一列,相对应的类型要一样)

    可以来自多张表的数据:多次sql语句取出的列名可以不一致,此时以第一个sql语句的列名为准。

    如果不同的语句中取出的行,有完全相同(这里表示的是每个列的值都相同),那么union会将相同的行合并,最终只保留一行。也可以这样理解,union会去掉重复的行。

    如果不想去掉重复的行,可以使用union all。

    如果子句中有order by,limit,需用括号()包起来。推荐放到所有子句之后,即对最终合并的结果来排序或筛选。

    注意:

    1、UNION 结果集中的列名总是等于第一个 SELECT 语句中的列名

    2、UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同

    UNION ALL的作用和语法:

    默认地,UNION 操作符选取不同的值。如果允许重复的值,请使用 UNION ALL。当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行。

    总结

    (1)对于要求全面的结果时,我们需要使用连接操作(LEFT JOIN / RIGHT JOIN / FULL JOIN);

    (2)应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

    备注、描述、评论之类的可以设置为 NULL,其他最好不要使用NULL。

    不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,如果是varchar这样的变长字段, null 不占用空间。

    可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

    select id from t where num = 0

    (3)in 和 not in 也要慎用,否则会导致全表扫描,如:

    对于连续的数值,能用 between 就不要用 in 了:

    很多时候用 exists 代替 in 是一个好的选择:

    (4)尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

    (5)尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

    (6)不要以为使用MySQL的一些连接操作对查询有多么大的改善,其实核心是索引


    相关文章

      网友评论

          本文标题:mysql优化学习

          本文链接:https://www.haomeiwen.com/subject/tubfixtx.html