【Mysql】我的实践笔记之Sql优化手段

转载出处:https://blog.csdn.net/m0_37657725/article/details/98964611

每条规则在某些条件下都会被打破。

目录

1 定位慢查询

2 选择合适的数据库引擎

3 选择合适的索引

3.1 索引的使用场景

3.2 索引技巧

4 数据库优化之分表

5 数据库的读写分离

6 数据库优化之缓存

7 sql语句优化

7.1 基本优化

7.2 不要让引擎放弃使用索引而进行全表扫描

7.3 合理使用索引问题

7.4 临时表问题

7.5 游标

8 调优参数


 

1 定位慢查询

slow_query_log= 1//开启慢查询
long_query_time= 100//查询超时时间
slow_query_log_file=c:/slow.log //指定超时sql语句存放目录

在项目当中,给测试人员执之前,再启动mysql数据的时候,我们开启慢查询,并且把慢查询语句打到我们的日志当中,运行一段时间,看看哪些语句执行效率最慢

 

 

2 选择合适的数据库引擎

show engines;
SHOW VARIABLES LIKE 'storage_engine';


在开发当中,我们经常用的存储引擎 :Myisam   innodb    memory

  • Myisam 存储引擎:MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物。
  • Innodb 存储引擎:InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键
  • Memory :Memory 存储引擎将表中的数据存储到内存中,当我们的数据频繁变化的时候,而且不需要入库,这个时候用memory存储引擎。

 

3 选择合适的索引

 

3.1 索引的使用场景

  1. where条件经常使用,如果不做查询基本上索引没有什么意义
  2. 该字段的内容不是唯一的几个值(过滤因子高才可以)
  3. 字段的内容不能频繁的变化

3.2 索引技巧

https://mp.csdn.net/postedit/100515924

 

4 数据库优化之分表

详细参考:https://segmentfault.com/a/1190000006158186
根据经验,mysql数据达到百万级别,查询效率就非常低了,就会造成锁表,甚至堆积多连接,导致mysql直接挂掉,水平分表就可以减少压力。如果一张表当中某个字段非常多(长文本,二进制),很少情况下查询,这个时候我们就要考虑把这些字段单独放到一个表中,通过外键关联起来。

分表策略

  • 按时间分表:微博,qq,我们想要查询月份动态,或者月份的缴费情况,这些有规律性的数据内容,我们可以按时间分表
  • 按区间分表:数据库当中的自增id
  • Hash分表:通过一个原始目标的id或者名称通过一定的合适算法,算出来数据库的表名称。然后访问相应的表。

垂直分表

水平分表

水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。

 

5 数据库的读写分离

背景:一台数据库支持最大的并发链接数量是有限的,如果用户访问量加大,一台服务器是满足不了我们的需求,所以我们用集群的方式来实现。 主从同步,读写分离

 

6 数据库优化之缓存

Java当中我们持久层和数据库这个层面 常见的缓存有hb的二级缓存,mb二级缓存,这些缓存都不支持分布式缓存,我们可以用redis来作为中央缓存

 

7 sql语句优化

7.1 基本优化

Mysql是个多用户多线程的DBMS,如果遇到性能不良,可使用SHOW PROCESSLIST显示所有活动进程,让管理员用

KILL命令终结某个特定的进程。

可以使用EXPLAIN语句让mysql解释它将如何执行一条SELECT语句。

如果一个简单的WHERE子句返回结果所花时间太长,可以断定其中使用的列(或几个列)就是需要索引的对象。


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

2、尽量使用数字型字段一部分开发人员和数据库管理人员喜欢把包含数值信息的字段 设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。比如设计学生信息表中的性别问题,使用 男、女的性能就不如使用数字1、2来代替。

3、尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。


4、合理使用exists ,not exists 子句。——如果你想校验表里是否存在某条纪录。

select sum(t1.c1) from t1 where (select count(*)from t2 where t2.c2=t1.c2>0)   
select sum(t1.c1) from t1 where exists(select * from t2 where t2.c2=t1.c2) 

5、两者产生相同的结果,但是后者的效率显然要高于前者。因为后者不会产生大量锁定的表扫描或是索引扫描。如果你想校验表里是否存在某条纪录。可以用EXISTS代替。如:

if exists (select * from table_name where column_name = ‘xxx’)

6、关键字的使用:能够用DISTINCT的就不用GROUP BY, 尽量不要用SELECT INTO语句。SELECT INTO 语句会导致表锁定,阻止其他用户访问该表。


7、能用UNION ALL就不要用UNION

 UNION ALL不会进行排序,去重。UNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源 在跨多个不同的数据库时使用UNION是一个有趣的优化方法,UNION从两个互不关联的表中返回数据,这就意味着不会出现重复的行,同时也必须对数据进行排序,我们知道排序是非常耗费资源的,特别是对大表的排序。 UNION ALL可以大大加快速度,如果你已经知道你的数据不会包括重复行,或者你不在乎是否会出现重复的行,在这两种情况下使用UNION ALL更适合。此外,还可以在应用程序逻辑中采用某些方法避免出现重复的行,这样UNION ALL和UNION返回的结果都是一样的,但UNION ALL不会进行排序。


8、程序中如果一次性对同一个表插入多条数据,拼成一条语句执行效率会更高,比如以下语句:

 insert into person(name,age) values(‘xboy’, 14), (‘xgirl’, 15),(‘nia’, 19);  

9、count的优化

比如:计算id大于5的城市的数量
a. select count(*) from world.city where id > 5;
b. select (select count(*) from world.city) – count(*) from world.city where id <= 5; 

a语句当行数超过11行的时候需要扫描的行数比b语句要多,b语句扫描了6行,此种情况下,b语句比a语句更有效率。
当没有where语句的时候直接select count(*) from world.city这样会更快,因为mysql总是知道表的行数。

10、or与union

利用union替换or

11、LIKE与FULLTEXT

一般来说,最好使用FULLTEXT而不是LIKE

 

7.2 不要让引擎放弃使用索引而进行全表扫描


where子句:

涉及的列带有索引,
避免:!= 或 <>
避免or :来连接条件,用union all连接
in 和 not in 也要慎用,对于连续的数值,能用 between 就不要用 in 了,否则用 exists 代替 in 是一个好的选择:
有null 值用0填充
避免模糊查询,like ‘%abc%’   若要提高效率,可以考虑全文检索。
索引字段上进行运算会使索引失效,尽量少使用参数和表达式
避免where子句中对字段进行函数操作


详解上面

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 子句中使用 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'

 

4. 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)

 

5. 避免在 where 子句中使用参数,否则会导致全表扫描

因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num = @num   可以改为强制查询使用索引:

select id from t with(index(索引名)) where num = @num

6. 应尽量避免在 where 子句中“=”左边进行函数、算术运算或其他表达式运算,这将导致引擎放弃使用索引而进行全表扫描。

select id from t where num/2 = 100    应改为:

select id from t where num = 100*2

函数:

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'

9.不要写一些没有意义的查询,如需要生成一个空表结构:

 

 

7.3 合理使用索引问题


索引并不是越多越好

索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

尽可能的避免更新 clustered 索引数据列

因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

 

7.4 临时表问题


1.尽量使用表变量来代替临时表。

2. 避免频繁创建和删除临时表

这样可以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件, 最好使用导出表。

3. 关于插入临时表

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

4. 如果使用到了临时表

在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

 

7.5 游标


1. 尽量避免使用游标

因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

2.使用基于游标的方法或临时表方法之前

应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

3.与临时表一样,游标并不是不可使用。

对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

 

8 调优参数

  • 批量插入几百万条数据时修改事务的提交方式: set  autocommit=1 关闭 //批量导入数据的时候
  • 将多条sql语句合成一条去执行,尽量不要用java代码去写,要用存储过程(最好用)。
  • 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
  • 对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。
  • select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。
  • 尽量避免大事务操作,提高系统并发能力。
  • 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

 

 

  • back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500
  • wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时
  • max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限
  • thread_concurrency:并发线程数,设为CPU核数的两倍
  • skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问
  • key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好
  • innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证(Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好
  • innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小
  • innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB
  • query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大.
  • 可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小
  • read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能
  • sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小
  • read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
  • record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值
  • thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的
  • table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM

 

相关好文参考:
一条SQL语句在MySQL中如何执行的
MySQL高性能优化规范建议
腾讯面试:一条SQL语句执行得很慢的原因有哪些?—不看后悔系列
一千行 MySQL 学习笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值