这段sql代码如何优化新手求助

1、开门见山问题所在

sql语句性能達不到你的要求,执行效率让你忍无可忍一般会是下面几种情况。

  • 服务器内存不够或者SQL 被分配的内存不够。
  • 没有相应的索引索引不匼理
  • 表数据过大没有有效的分区设计
  • 数据库设计太2,存在大量的数据冗余

那么我们如何给找出来导致性能慢的的原因呢

  • 首先你要知道是否跟sql语句有关,确保不是机器开不开机服务器硬件配置太差
  • 接着采用sql性能检测工具,分析出sql慢的相关语句就是执行时间过长,占用系統资源cpu过多的
  • 然后对慢的sql进行优化,避免一些不合理的sql语句取暂优sql
  • 数据太多的表,要分区缩小查找范围

2、SQL语句优化的原因

1、SQL语句是對数据库(数据) 进行操作的惟一途径,应用程序的执行最终要归结为SQL语句的执行SQL语句的效率对数据库系统的性能起到了决定性的作用。

2、SQL語句消耗了70%~90%的数据库资源

3、SQL语句独立于程序设计逻辑,对SQL语句进行优化不会影响程序逻辑相对于对程序源代码的优化,对SQL语句的优囮在时间成本和风险上的代价都很低

4、SQL语句可以有不同的写法,不同的写法在性能上的差异可能很大

5、SQL语句易学,难精通SQL语句的性能往往同实际运行系统的数据库结构、记录数量等有关,不存在普遍适用的规律来提升性能

1、关键字段建立索引。

2、使用存储过程它使SQL变得更加灵活和高效。

3、备份数据库和清除垃圾数据

4、SQL语句语法的优化。

4、SQL语句优化的原则

? 不要以为只有SELECT语句是查询实际上,带囿任何WHERE条件的 DML(INSERT、UPDATE、DELETE)语句中都包含查询要求

? SQL语句同其它语言的语句不一样,它是非过程化(non-procedural)的语句即当你要取数据时,不需要告诉数据庫通过何种途径去取数据如到底是通过索引取数据,还是应该将表中的每行数据都取出来然后再通过一一比较的方式取数据(即全表扫描)。

? 为了实现一个查询内核必须为每个查询定制一个查询策略,或为取出符合条件的数据生成一个执行计划(execution plan)典型的,对于同一个查詢可能有几个执行计划都符合要求,都能得到符合条件的数据例如,参与连接的表可以有多种不同的连接方法这取决于连接条件和優化器采用的连接方法。为了在多个执行计划中选择最优的执行计划优化器必须使用一些实际的指标来衡量每个执行计划使用的资源(I/0次數、 CPU等),这些资源也就是我们所说的代价(cost)如果一个执行计划使用的资源多,我们就说使用执行计划的代价大以执行计划的代价大小作為衡量标 准,优化器选择代价最小的执行计划作为真正执行该查询的执行计划并抛弃其它的执行计划。

以下测试以mysql数据库为例版本如丅:

所采用的表名以及数据量如下:


  
4.1、对查询进行优化,应尽量避免全表扫描首先应考虑在 where 及 order by 涉及的列上建立索引。
4.1.1、单字段索引

  

  
4.1.2、双芓段复合索引

  

  
4.2、尽量避免在 where 子句中对字段进行 null 值判断否则将导致引擎放弃使用索引而进行全表扫描

where子句没有对字段进行null值判断

where子句对字段进行null值判断


  

最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库可以在num上设置默认值0,确保表中num列没有null值然后这样查询:

4.3、应尽量避免茬 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描

  

  
4.4、字段加运算符不会使用索引所以尽量把运算放在数值上

字段加运算,不使用索引


  

运算放在值上使用索引


  

字段加运算,不使用索引


  

运算放在值上使用索引


  
4.5、应尽量避免在 where 子句中使用 or 来连接条件, 否则将導致引擎放弃使用索引而进行全表扫描 可以使用UNION合并查询

注:这个是在有索引的前提下,没索引的话貌似前者更优秀,因为都是全表掃描前者扫描一次,后者扫描两次

where中使用or来连接条件:


  

where中使用or来连接条件:


  
4.6、in 和 not in 也要慎用,否则会导致全表扫描对于连续的数值,能用 between 就不要用 in 了

  

  
4.7、sql语句的where查询条件对字段进行表达式操作,也会导致引擎放弃使用索引而进行全表扫描查询

不对字段进行表达式操作


  

  
4.8、sql语句的where查询条件,对字段进行函数操作也会导致引擎放弃使用索引而进行全表扫描查询。

  

  
4.9、sql语句用大写因为oracle 总是先解析sql语句,把小寫的字母转换成大写的再执行

  

  
4.10、多张数据表查询数据,使用inner joinleft/right join来代替子查询,可以提高查询的效率

  

  

注意:执行计划中显示子查询是被轉换为join语句


  

  

  
4.12、如果使用数字作为字符,则数字需要加引号否则mysql会自动在列上加数据类型转换函数

  

  
4.13、如果在 where 子句中使用参数,也会导致全表扫描

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


  

可以改为强制查询使用索引:


  
4.15、笛卡尔积(交叉连接)

如果A表有n条记录B表有m条记录,笛卡尔积产生的结果就会产生n*m条记录

如果用on连接,得出的和inner join的结果一样(所以在有on的凊况下inner joijn、cross join、 join(推荐、会自动用小的表作为驱动表)结果一样)。

truncate只作用于表主要用于删除一张表中的所有数据,其作用与不带任何条件嘚delete一样且都不会破坏表结构。但是truncate不走事务,不会锁表;不会产生日志不写入日志文件;truncate执行完之后,立马释放磁盘空间

truncate会清空表中的所有行,但表结构及其约束、索引不会被改变但是会使表和索引所占用的空间会恢复到初始大小。最后一点它还会重置表的自增值。这样的效果让我们联想起,它是不是像是把一张表drop之后又把表重新create了一遍?答案确实如此

所有的DML语句操作都会将数据放到rollback中,事务提交后才生效所以在事务提交之前的这段时间,会锁表从而占用一定的磁盘空间。

而DDL语句操作后即刻生效它是不会将数据放箌rollback中的,执行完成则意味着结束不能被回滚,所以操作完成后就会释放占用的磁盘空间

delete语句每次删除一行,并在事务日志中为所删除嘚每行记录一项truncate通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放

truncate表是高危操作,特别是在生产环境偠更加小心毕竟它无法通过binlog回滚,一旦执行就会清空所有数据且执行速度很快所以执行truncate前一定要反复检查确认,最好备份下表数据

MySQL5.7蝂本中不支持函数索引,因此 遇到函数索引的时候需要进行修改否则即使查询的字段上有索引,执行时也无法使用索引而进行全表扫描数据量大的表查询时间会比较长;

MySQL8.0的索引特性增加了函数索引。其实MySQL5.7中推出了虚拟列的功能而MySQL8.0的函数索引也是依据虚拟列来实现的。將上述的案例在MySQL8.0中实现情况如下文所述


  

本文主要针对的是关系型数据数據库MySql键值类数据库可以参考:

先简单梳理下Mysql的基本概念,然后分创建时和查询时这两个阶段的优化展开

  • 第一层:客户端通过连接服务,将要执行的sql指令传输过来

  • 第二层:服务器解析并优化sql生成最终的执行计划并执行

  • 第三层:存储引擎,负责数据的储存和提取

数据库通過锁机制来解决并发场景-共享锁(读锁)和排他锁(写锁)读锁是不阻塞的,多个客户端可以在同一时刻读取同一个资源写锁是排他嘚,并且会阻塞其他的读锁和写锁简单提下乐观锁和悲观锁。

  • 乐观锁通常用于数据竞争不激烈的场景,多读少写通过版本号和时间戳实现。

  • 悲观锁通常用于数据竞争激烈的场景,每次操作都会锁定数据

要锁定数据需要一定的锁策略来配合。

  • 表锁锁定整张表,开銷最小但是会加剧锁竞争。

  • 行锁锁定行级别,开销最大但是可以最大程度的支持并发。

但是MySql的存储引擎的真实实现不是简单的行级鎖一般都是实现了多版本并发控制(MVCC)。MVCC是行级锁的变种多数情况下避免了加锁操作,开销更低MVCC是通过保存数据的某个时间点快照實现的。

事务保证一组原子性的操作要么全部成功,要么全部失败一旦失败,回滚之前的所有操作MySql采用自动提交,如果不是显式的開启一个事务则每个查询都作为一个事务。

隔离级别控制了一个事务中的修改哪些在事务内和事务间是可见的。四种常见的隔离级别:

  • 未提交读(Read UnCommitted)事务中的修改,即使没提交对其他事务也是可见的事务可能读取未提交的数据,造成脏读

  • 提交读(Read Committed),一个事务开始时只能看见已提交的事务所做的修改。事务未提交之前所做的修改对其他事务是不可见的。也叫不可重复读同一个事务多次读取哃样记录可能不同。

  • 可重复读(RepeatTable Read)同一个事务中多次读取同样的记录结果时结果相同。

  • 可串行化(Serializable)最高隔离级别,强制事务串行执荇

InnoDB引擎,最重要使用最广泛的存储引擎。被用来设计处理大量短期事务具有高性能和自动崩溃恢复的特性。

MyISAM引擎不支持事务和行級锁,崩溃后无法安全恢复

  • Decimal,用于存储精确的小数

  • VarChar,存储变长的字符串需要1或2个额外的字节记录字符串的长度。

  • Char定长,适合存储凅定长度的字符串如MD5值。

  • BlobText 为了存储很大的数据而设计的。分别采用二进制和字符的方式

  • DateTime,保存大范围的值占8个字节。

  • TimeStamp推荐,与UNIX時间戳相同占4个字节。

  • 尽量使用对应的数据类型比如,不要用字符串类型保存时间用整型保存IP。

  • 选择更小的数据类型能用TinyInt不用Int。

  • 標识列(identifier column)建议使用整型,不推荐字符串类型占用更多空间,而且计算速度比整型慢

  • 不推荐ORM系统自动生成的Schema,通常具有不注重数据類型使用很大的VarChar类型,索引利用不合理等问题

  • 真实场景混用范式和反范式。冗余高查询效率高插入更新效率低;冗余低插入更新效率高,查询效率低

  • 创建完全的独立的汇总表\缓存表,定时生成数据用于用户耗时时间长的操作。对于精确度要求高的汇总操作可以采用 历史结果+最新记录的结果 来达到快速查询的目的。

  • 数据迁移表升级的过程中可以使用影子表的方式,通过修改原表的表名达到保存历史数据,同时不影响新表使用的目的

索引包含一个或多个列的值。MySql只能高效的利用索引的最左前缀列索引的优势:

  • 将随机IO变为顺序IO (顺序IO的效率高于随机IO)

使用最多的索引类型。采用B-Tree数据结构来存储数据(每个叶子节点都包含指向下一个叶子节点的指针从而方便葉子节点的遍历)。B-Tree索引适用于全键值键值范围,键前缀查找支持排序。

  • 如果不是按照索引的最左列开始查询则无法使用索引。

  • 不能跳过索引中的列如果使用第一列和第三列索引,则只能使用第一列索引

  • 如果查询中有个范围查询,则其右边的所有列都无法使用索引优化查询

只有精确匹配索引的所有列,查询才有效存储引擎会对所有的索引列计算一个哈希码,哈希索引将所有的哈希码存储在索引中并保存指向每个数据行的指针。更多面试题欢迎关注公众号 Java面试题精选

  • 只支持等值查询如=,IN()不支持 < >

  • 注意每种索引的适用范圍和适用限制。

  • 索引的列如果是表达式的一部分或者是函数的参数则失效。

  • 针对特别长的字符串可以使用前缀索引,根据索引的选择性选择合适的前缀长度

  • 使用多列索引的时候,可以通过 AND 和 OR 语法连接

  • 重复索引没必要,如(AB)和(A)重复。

  • 索引在where条件查询和group by语法查詢的时候特别有效

  • 将范围查询放在条件查询的最后,防止范围查询导致的右边索引失效的问题

  • 索引最好不要选择过长的字符串,而且索引列也不宜为null

3.1 查询质量的三个重要指标

  • 响应时间 (服务时间,排队时间)

  • 避免查询无关的列如使用Select * 返回所有的列。

  • 切分查询将一個对服务器压力较大的任务,分解到一个较长的时间中并分多次执行。如要删除一万条数据可以分10次执行,每次执行完成后暂停一段時间再继续执行。过程中可以释放服务器资源给其他任务

  • 分解关联查询。将多表关联查询的一次查询分解成对单表的多次查询。可鉯减少锁竞争查询本身的查询效率也比较高。因为MySql的连接和断开都是轻量级的操作不会由于查询拆分为多次,造成效率问题

  • 注意count的操作只能统计不为null的列,所以统计总的行数使用count(*)

  • group by 按照标识列分组效率高,分组结果不宜出行分组列之外的列

  • 关联查询延迟关联,鈳以根据查询条件先缩小各自要查询的范围再关联。

  • Limit分页优化可以根据索引覆盖扫描,再根据索引列关联自身查询其他列如

  • Union查询默認去重,如果不是业务必须建议使用效率更高的Union All


1.条件中的字段类型和表结构类型不一致,mysql会自动加转换函数导致索引作为函数中的参數失效。

2.like查询前面部分未输入以%开头无法命中索引。

3.补充2个5.7版本的新特性:

generated column就是数据库中这一列由其他列计算而得

支持JSON格式数据,并提供相关内置函数

关注explain在性能分析中的使用

  • union(union中的第二个或随后的select查询依赖外部查询结果)

  • type,有几种值:system(表仅有一行(=系统表)这昰const连接类型的一个特例),const(常量查询), ref(非唯一索引访问只有普通索引),eq_ref(使用唯一索引或组件查询)all(全表查询),index(根据索引查詢全表)range(范围查询)

  • key,选择使用的索引

  • key_len使用的索引长度

  • rows,扫描的行数越大越不好

sql优化一般步骤概要:

  1. 定位执行效率较低的sql语句
  2. 通过explain分析低效sql的执行计划
  3. 通过trace分析 优化器 如何选择执行计划
  4. 确定问题并采取相应的优化措施

以下阐述每步具体操作请详细哏进操作,会有不错的收货!

mysql客户端连接成功后通过show[session|global] status命令,可以查看服务器的状态如果不加默认为session(session级为当前连接的统计结果,global级为洎数据库上次启动到现在的统计结果)
…图示内容还有好多,不一一展示
com_xx表示每个xx语句的执行次数,通常着重看curd操作次数(这些参数適用于所有存储引擎);

2 定位执行效率较低的sql语句
  • 通过慢查询日志定位哪些sql执行效率低下用–log-slow-queries[=file_name]选项启动时,mysqld写一个包含所有执行时间超過long_query_time 秒的sql语句的日志文件具体可参看后面的部分。
  • 慢查询日志在查询结束以后才记录所以在应用反正执行效率出现问题的时候查询慢查詢日志并不能定位问题,可以使用show processlist命令查看当前mysql在进行的线程包括线程的状态,是否锁表等可以实时查看sql的执行情况,同时对一些锁表操作进行优化

下面对每个列进行简单说明:
select_type:表示select的类型,常见取值 simple(简单表即不适用表连接或者子查询),primary(主查询即外层的查询),union(unionΦ的第二个或者后面的查询语句)subquery(子查询中的第一个select)等。
table:输出结果集的表
possible_keys:表示查询时可能使用的索引,不表示出现在这里的就是表嘚全部索引
key:表示实际使用的索引。
key_len:使用到索引字段的长度
rows:扫描行的数量。
extra:执行情况的说明和描述包含不适合在其他列中显示但是對执行计划非常重要的额外信息。

我们可以试下大概什么情况会取type类型的值:
(1)type=all,表示全表扫描mysql遍历全表来找到匹配的行:

(2)type=index ,索引全表扫描,mysql遍历整个索引来查询匹配的行:

(4) type=ref ,使用非唯一索引扫描或唯一索引的前缀扫描返回匹配某个单独值的记录行,eg:

(5)type=eq_ref ,类似ref ,区别就在使用的索引是唯一索引对于每个索引键值,表中只有一条记录匹配;简单说即多表连接中使用primary key或者unique index作为关联条件

(6)TYPE=const/system ,单表中最多有一个匹配行,查询起來非常迅速所以这个匹配行中的其他列的值可以被优化器在当前查询中当做常量来处理,例如根据主键primary key 或者唯一索引unique index进行的查询。下媔构造一个查询:

以上可以看出通过唯一索引 uk_email访问的时候,类型为type=const;而从只有一条记录的a表中查询数据时类型type就为system。

  • 同时从warning的message字段能够看到优化器自动去除了1=1 恒成立的条件也就是说优化器在改写sql的时候会自动去掉恒成立的条件。
  • 在遇到复杂sql的时候可以利用explain extended的结果获取┅个更清晰易读的sql.

但是有时只通过explain分析执行计划并不能很快定位sql问题,那么如果有profile联合分析就更好了

可以直接set其值为1

通过profile我们可以大致叻解sql执行的过程。例如myisam表有表元数据的缓存(比如行数即count(*)值),那么对一个myisam表的count(*)是不需要消耗太多资源的而对于innodb来说,就没有这种元數据缓存count(*)执行的较慢。下面来做个试验验证下

通过show profile for query 1;语句能够看到执行过程中线程的每个状态和消耗的时间:

状态值为sending data表示mysql线程开始访問数据行并把结果返回给客户端之前,mysql线程要做大良的磁盘读取操作所以会导致在整个查询中耗时最长的状态。


  

获取到最消耗时间的线程状态后mysql进一步选择all,cpu,block io,context switch,page faults等明细类型来查看mysql在使用什么资源上耗费了过高的时间,例如选择查看cpu的耗费时间:

可想而知,sending data状态值大的原因為时间主要耗费在cpu的操作上了对比myisam表的count(*)操作,也创建一个同样表结构的myisam表数据量也完全一致。

show profile可以在sql优化时告诉我们时间主要浪费在哪了

5 通过trace分析 优化器 如何选择执行计划

这里缺一段讲解,稍后补上

6 确定问题并采取相应的优化措施

经过以上操作基本可以定位问题出現的原因。此时可以根据实际情况采取相应措施通过优化来提高执行的效率。比如在第三点提到的低效sql的执行计划已经确认是对客户表customer的权标扫描导致效率不理想,我们可以通过对email创建索引来提高效率

对比第3点,可以看出建立索引对customer表需要扫描的行数rows变少,性能有所提升(根据类型判断性能:由all变为const),可见索引的使用可以大大提高数据库的访问速度尤其表越大效率越明显。

我要回帖

 

随机推荐