来,我们讨论下如何怎么才能让Sql执行的快。

发现问题

  • 系统业务性能表现
  • Mysql慢日志。看这里
  • Mysql主机 CPU负载过高
  • RDS等云数据库得监控
  • 增加慢sql层,比如利用Spring AOP重写数据源,增加慢Sql告警
  • explain命令发现使用where条件而没有命中任何索引,或者是为了得到返回结果用到了太多的行.返回结果中。type对应了查询所使用的类型,比如All代表全表扫描,ref代表索引扫描,还会有范围扫描、唯一索引扫描等等。最好都能够达到ref的级别。

Mysql 整体结构

在解决慢Sql问题之前,先看一下Mysql的整体结构和一个查看Sql的执行过程。

  • Connection Pool 连接池, 可以通过设置max_connections=200增加连接数提高并发性能,但是会增加CPU消耗和增加锁的开销。
  • Parser 解析器。 会把Sql解析成 语法树校验语法正确, 且 缓存语法树
  • Optimizer 优化器, 重新决定表的顺序, 选择索引,缓存命中
  • Cache & Buffer 缓存。 两个部分,查询缓存和索引缓存。innodb_buffer_pool_size 设置索引缓存。基本上越大越好。

查询缓存真的会快么?

很重要的三点:

  • 可以缓存执行计划,省去解析和生成执行计划的过程
  • 可以缓存数据,只有sql完全匹配才会命中缓存
  • 只要相关的表发生了变化,缓存即刻失效
    好下面来评价一下它是否会有用。
  • 会缓存执行计划,这个是有用的,这个也是为什么视图会稍微快一点的原因,因为视图是被Parser和Optimizer构造成执行计划缓存的。
  • 完全匹配包含了空格等等都不是完全匹配,有不确定因素不算完全匹配比如含有now()
  • 也就是任何的插入更新都会导致失效
  • 缓存是排它锁的,会成为瓶颈。(缓存更新的时候,查询是阻塞的)
  • 评价缓存的唯一标准,缓存命中率,很低。
    总结:基本上不会有人依赖于这个缓存的。

那么Buffer有用么?有,大用,后面说。

一些写Sql的基本原则和误解

写好SQL的唯一原则,就是在脑子中模拟数据库是如何做过滤的,这个前提是需要了解很多的知识,先来看一下一些基本的原则和容易的误解。

用具体的字段代替*么?

测试一下好了。

SELECT * FROM fm_news_news WHERE id=95950;
SELECT id FROM fm_news_news WHERE id=95950;

除了减少了IO外, *还会对应一个字典映射。

越小的条件越在右面么?

SELECT * FROM fm_news_news WHERE create_org = 3 AND date_publish > '2016-08-30 23:59:59';
调换顺序和EXPLAIN之后发现:是一样一样一样的啊。 为什么呢?
因为是全表扫的,每扫一行都是匹配这两项,不管什么位置都是要全表扫。

然后我们换一个带索引的SQL:

EXPLAIN SELECT * FROM fm_news_news WHERE id>95950 AND date_update > '2016-08-30 23:59:59';

调换位置会发现还是一样的,这个也是因为Optimizer发挥了作用,会初步计算会过滤掉的行,然后决定选取的索引,
唯一索引>普通索引, 其他的索引是先定义的索引,而不是SQL的位置。

OR 替代 IN么?

实际上Optimizer会把IN自动改成OR的格式,没有必要显示的修改。

hibernate 的 n+1问题。

hibernate设置延迟加载的时候会有N+1查询数据库的问题,
可以强制走左连接。

EXIST代替IN么?

首先应用场景不一样,EXIST仅仅适用于子查询。

SELECT * FROM fm_news_news n WHERE EXISTS (SELECT 1 FROM fm_news_channelnews cn WHERE n.id = cn.news_id AND cn.channel_id=120 AND cn.channel_type = 5)
SELECT * FROM fm_news_news WHERE id IN (SELECT news_id FROM fm_news_channelnews WHERE channel_id=120 AND channel_type = 5);
SELECT * FROM fm_news_news n LEFT JOIN fm_news_channelnews cn ON n.id=cn.news_id AND cn.channel_id=120 AND cn.channel_type = 5;
// 上面这个会先查出一个channel_news的临时表,然后用news_news跟这个临时表LEFT JOIN, 所以很低是正常的
SELECT * FROM fm_news_news n LEFT JOIN fm_news_channelnews cn ON n.id=cn.news_id WHERE  cn.channel_id=120 AND cn.channel_type = 5;

2, 4的性能最好。
都会命中索引,网上有的说IN语句的子查询不会命中索引是错误的。

2比1块为什么?
首先看IN是怎么查的,IN中()内的子查询只执行一次,返回结果集int[] 然后是便利for(int i : int[]) 在便利中找到主表中满足要求的。
而EXIST呢,先执行A的全表扫描得到结果集News[] ns 然后便利这个ns, 判断每一条在EXIST条件中是否能返回true,所以本质是一个EXIST的全表扫。
选对驱动表, IN是子查询是驱动表,EXIST是外面是驱动表

IN不处理null

SELECT * FROM fm_news_news WHERE brief IN (NULL) LIMIT 10这样查询的结果为空,只能使用IS NULL;
但是有null的列都不入索引,所以is not null或者是is null 语句都不走索引,因此我们插入数据的时候索引字段不能为null

实际上决定快慢主要有如下的因素:
1. 两个表的数据量,以及数据分布;
2. 表有没有经过分析;
3. 子查询中是否包含NULL值 (很重要);
4. 是否存在索引;
5. 数据库版本:不同版本的数据库,优化器的工作方式会有差异. 最新版本IN子查询已经可以走索引了

用NOT EXIST代替NOT IN么?

NOT EXIST 仍然是以左表作为驱动表,做全查询,然后判断条件是否成立的。

SELECT * FROM fm_user_devicechannel dc WHERE dc.channel_id NOT IN (NULL);
 SELECT * FROM fm_news_channelnews cn WHERE NOT EXISTS (SELECT 1 FROM fm_user_defaultchannel df WHERE cn.channel_id = df.channel_id );
SELECT * FROM fm_user_devicechannel dc WHERE dc.channel_id NOT IN (SELECT channel_id FROM fm_user_defaultchannel df);

NOT IN null的这个会返回空。 所以只有需要判断子查询的结果中不为null的时候才有意义。

其他的情况下因为NOT EXIST会使用索引, NOT IN 是完全的双表全表扫所以最慢,理论上讲永远不用。

先过滤后GROUP BY, 而不是先GROUP BY 再HAVING

尽量使用一个复杂语句而不是多次链接数据库么?

这个越来越不是这样了,特别是随着SOA,微服务的盛行。实际上拆分多个语句会有如下的好处:

  • 让缓存效率更高。 如果第一个查询已经执行过,那么就可以跳过。
  • 减少锁竞争
  • 在应用层做关联,可以对数据库进行拆分,获得更好的扩展性
  • 使用IN()代替关联查询,本身会比关联查询更高效
  • 在应用层可以重用第一次的查询结果,比如做缓存。

下面是索引,单独拿出来说

索引

为什么索引会变快?

是由索引的数据结构决定的。索引是一个二叉树,二叉树的便利过程就是一个二分查找算法。查找的复杂度为logn,n是树的高度。
因为效率跟树的高度有关,所以一般索引都是平衡二叉树,平衡二叉树就是两边的高度尽量一样高。这种再插入的时候会增加左旋和右旋,但是查询效率很高。
再进化就是B+ tree了,如下图的结构:

可见每个节点是多个值,这个主要是因为磁盘IO的读取性质, 连续读远远>随即读, B+Tree的一个节点会放在连续的空间,然后通过系统的页加载机制,一次读出多个数据,大大的减少磁盘I/O次数,增加性能。

通过上面的方法保证了有索引的查询会比较快。

索引一定快么?

  • 不一定,首先如果不是很唯一的值就不一定快。
  • 另外因为需要先查找索引页在查找数据页,一般当使用索引仍然需要查询70%以上数据的时候索引就比不用索引还慢了
  • 如果数据特别少也是不用索引比较快。比如在数据少的时候冒泡比二分查找还快。

聚簇索引、非聚簇索引、覆盖索引

聚簇索引:
类似于没有目录的字典,表的数据就是按照索引的顺序来存储的,既叶子节点就是表数据。 所以一张表上最多只有一个。更新的性能差。
但是因为物理上的位置就是相邻的,所以呢范围查找会比较快。
还有一个问题是容易出现尾端热点,可能有性能问题。
非聚簇索引:
叶子节点是到真实物理数据的指针和索引字段的内容,类似于目录。
插入的性能会好
范围查询就没有这么大的优势了。
覆盖索引: 所有的数据都是索引字段,直接在内存中便利索引就拿到了,不需要。

索引命中的问题

  • 连接列不会用索引 a||""||b
  • like 右通配符能命中索引 'a%' 左通配符不行'%b'
  • 组合索引,(a, b, c) 查询 a AND b, 或者是a 但是如果是 b AND c的这种就不行
  • != 不会使用索引,改为范围
  • 更新少,唯一性高,短列比较适合做索引
  • 对列的函数计算不会用到索引, 比如md5(colume1)
  • 函数计算针对常量则没有问题,比如 date_publish > DATE_FORMAT(now(), '')
  • NULL 字段不会命中索引 IS NULL 和 IS NOT NULL都要少用
  • 索引字段都命中索引,则会变成覆盖索引,效率更高
  • 很重要的一点, LEFT JOIN 的时候ON的语句一定是要有索引的,否则会变成全表查。

承接前面的Buffer,这个就是用来保存索引的, 可以用innodb_buffer_pool_size来设定,一般这台机器的所有内存的70-80%就是给他了。

分库分表下的查询优化

  • 全局表。
    插入更新的时候会同步到所有的分片。
    查询的时候使用一个分片。
  • ER Join
    表分组的思路, 子表的存储位置依赖于主表,解决Join的问题。
  • Share Join
    支持两张表, 解析SQL语句,分别查询,然后再聚合
时间: 2024-09-07 18:55:38

来,我们讨论下如何怎么才能让Sql执行的快。的相关文章

锁定状态下 Win7怎么样才能关机

现在大部分的系统,在锁定状态都是有个关闭电脑的图标的.但是如果你的系统没有,那么怎么样关机呢?大部分人用的方法是先登录再关机,所谓的锁定状态通常是指电脑在登录界面. 1 在windows xp和windows 2000系统如果在登录界面没有关闭电脑按钮.那么小编通过是通过更改注册表的设置来搞定的. 2 进入注册表,开始运行输入"regedit"就行了. 定位到:HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionWinl

win7系统按下shift键才能打开右键菜单的解决方法,右键菜单打不开?

  大部分的用户都了解,在win7系统中,如果要调出一个程序或者是文件的右键菜单,只需要选中该程序或者该文件的图标,鼠标点击右键,右键菜单就会自动的弹出来了.但此功能对于我们正常操作电脑带来便捷.不过有用户反映,必须要按下键盘上的shift键才能打开这个右键菜单,这是怎么回事呢?遇到这种问题我们应该怎么办呢?下面小编就跟大家一起分享下该问题的解决方法! 方法如下: 1.按下win7系统win+R组合快捷键,打开运行窗口,输入regedit字符命令并单击回车; 2.在打开的注册表编辑器窗口中,依次

开机总是要按下F1键才能继续的解决办法

1开机按F1键,这是因为你的BIOS设置不当但也能正常引导系统,属非致命性故障,按F1是在问你是否继续. 你没有安装软盘驱动器,但是在CMOS中设置了,开启软驱的选项. 方法是:开机按Del键,进入BIOS设置,选择第一个"基本设置",把floopy一项设置没"Disabel"无效就可以了. 2刚开始开机时按DEL进入BIOS,按回车键进入第一项,看看里面的"Drive A"项是不是"None",不是的话按"Pgup

ajax 调用后台方法大家可以讨论下_AJAX相关

我曾使用过的方法有三种: 1 在页面中加入隐藏的button,并定义了事件,然后执行js的$("#隐藏btnID").click()来 执行后台方法 2 用ajaxPro来执行 3 用ajax的url:页面地址/(静态)方法名称 来执行 在这种方法中,方法必须要求是静态的,所以例如在_default这个页面中 ,示例如下 复制代码 代码如下: public partial class _Default : System.Web.UI.Page { static int sVar = 1

fence 线程-Fence?android中的东西 你们有听说过吗?进来一起讨论下

问题描述 Fence?android中的东西 你们有听说过吗?进来一起讨论下 fence,看过源代码的人肯定不会陌生,中文是栅栏的意思,用在android里好像是为了解决cpu调度时乱序的问题,也就是串行操作?? 我不清楚,有大神出来一起讨论下吗,小弟在研究surfaceflinger,里面太多fence 至今没搞清楚它的机制.....而且网上一点资料都没有!!!让这个问答成为第一份资料吧,日后要是我研究有所结果定会分享!! 下面贴出它类的定义 : 在frameworks/native/incl

javase java-Java jbutton 点击一次 实现这个按钮往下移动 怎么才能实现????

问题描述 Java jbutton 点击一次 实现这个按钮往下移动 怎么才能实现???? Java jbutton 点击一次 实现这个按钮往下移动 怎么才能实现????

识别-讨论下中文按拼音排序的问题

问题描述 讨论下中文按拼音排序的问题 最近在做一个项目,需要根据电影名称的拼音进行排序,举个"栗子"(电影名捏造的): 功夫, 功夫熊猫, 美国梦, 重量级选手, 重庆的山, The Beautiful Life 期待的排序结果为:重庆的山 - 功夫 - 功夫熊猫 - The Beautiful Life - 重量级选手 好了,我们先看下Windows7是怎么排序的. win7的排序结果为:The Beautiful Life - 功夫 - 功夫熊猫 - 美国梦 - 重量级选手 - 重

c语言-讨论下这两个函数fact1和fact2的效率

问题描述 讨论下这两个函数fact1和fact2的效率 int fact1(int limit) { for (i=1;i<limit;i++) fact *= i; } int fact2(int limit) { for (i=limit; i!=0; i--) fact *= i; } 这个是当时上学老师出的一道题,我也觉得应该效率一样,不知道各位有什么不同看法 根据10楼兄弟的回答,我用objdump的反汇编结果如下: 00000000004004c2 <fact1>: 4004

c#编写工控代码&amp;amp;lt;发 动 机 耐 久 测试系统&amp;amp;gt;已开发完,请老鸟进来讨论下以解决的问题和有待解决的问题

问题描述 在这里主要想讨论下成在的问题,分享以总结的经验由于设计的方面比较广,相关内容会在该帖中部分列出部分原代码可以提供需要的人 解决方案 解决方案二:关于程序数据存储-1版以access数据+文本模式Access用来存储比较复杂的结构,文本用来保存大量的数据,防止Access数据库数据太大造成系统不稳定.解决方案三:Ad-di模块设计说明采集模块为时时的独立线程模块解决方案四:报警条件警告停机