在实际工作中,通常会遇到某个进程或者请求运行的十分缓慢,其中大部分的时间都花在了数据库的查询和写入上,每次遇到这个问题就十分头痛。首先,鉴于我们是很底层的程序员,没法花钱给企业加硬件,那我们能完成的事情就是用头脑取分析并优化每一条查询,来获得查询效率的提升。
老高总结了一下工作中遇到的问题,以后再遇到相同的问题后可以快速排查。
下面的表应该能很直观的看到从SQL的发送到结果的返回之间都经历了什么,我们需要从每一点触发尽量降低计算和IO消耗,已达到提升效率的目的。
以下过程都需要耗时耗资源
1.应用程序与数据库服务器建立链接
2.sql发送到数据库,数据库验证是否有执行的权限
3.进入语法解析器,进行词法与语法分析
4.进入优化器生成执行计划,部分dbms会检查是否有可重用的执行计划
5.根据执行计划依次扫描相关表中的行,不在数据缓冲区的走io
6.同时对于被扫描的行可能加锁,同时也可能会被其他sql阻塞
7.扫描的行足够放入查询缓存则开始运算或直接返回,不够则生成临时表,可能消耗io
8.对sql结果进行计算(可能)
9.将计算完成的结果全部写入网络io(可能)
10.如果事务完成则同步事务日志并释放锁,具体方式取决于dbms和当前配置
11.关闭连接(可选)
作者:张延俊
链接:https://www.zhihu.com/question/29619558/answer/45805380
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
理解数据库设计
正确使用索引
这一点很难,需要写大量的查询练习。
学会预估
在创建数据库或者数据表时,是否能够预估到将来会处理的数据量,数据的字段大小设置的是否合理,读和写的比率,具体点就是哪些数据会经常更新,哪些数据会经常被统计。 是否需要考虑并发 将来是否需要拆分
轻易不要使用 Select *
sql的查询一定要有目的性,错误的使用还会导致索引的失效,而且如果有一些用不到的字段很大,不是白白浪费了内存和带宽么。
真实例子,我处理的业务中有一个用不上的字段(类型是TEXT)可能很大,一般的查询不会出错,在某一次进程处理某一批数据的时候总是会报错内存过大而导致进程退出,最后发现原来是之前的人写的SELECT * 导致的,这种错误完全可以用指定列处理。
写清晰简单的SQL
这一点不用过多解释,SQL简单了解析器也会很高兴的。
尽量在不要使用复杂函数
尤其是where条件,日期一类的选择尽量在程序层面处理,不要用在SQL中使用date等时间处理的函数。如果实在需要,可以考虑函数索引,比如CREATE INDEX title_idx ON content (upper (content.title));
,这样就在upper (content.title)上建立了索引。
善用EXPLAIN
explain通常放在SELECT之前,他可以告诉你SELECT的执行计划,包括如何执行查询,需要扫描的行数等,这里我们需要尽量的命中索引,减少扫描。
MYSQL的文档EXPLAIN Output Format,中文解释请输入链接描述
扫描性能消耗从大到小 全表扫描>全索引扫描>部分索引扫描>索引查找>唯一索引/主键查找>常量/null
使用长链接
很多语言都有类似p_connect()
的方法,可以复用之前建立的数据库链接,以减少不必要的资源消耗,类似http的长连接。
使用LIMIT
如果确定只需要一条数据,就加上LIMIT 1
。LIMIT在分页查询时也很重要,关于LIMIT分页的优化,可以参考MySQL分页查询优化
IP
IP最好处理成整数保存,这样有利于计算和对比和计算。
合理使用触发器
老高的项目中有一个需求是记录变动日志,包括很多字段的变动都需要记录,比如IP,mac,这些变化写在代码里又臭又长,这时触发器就派上了用场,首先我们可以在某一个表的insert、update等时间上建立触发器,当发现新数据和旧数据有不同的时候自动执行一句insert log,岂不美哉。
数据库缓存
这一点不具体说明,主要是借助了nosql技术将数据写入Redis,leveldb等高性能数据库中,不过使用前请先浏览缓存更新的套路为上。
日志分析
PG獾 MYSQL工具很多,推荐mysqlsla和Percona Toolkit
分库分表
有这个需求的基本是上面的办法都没有解决,那么就需要对数据库进行架构的调整,最简单的方法就是分表,分表有垂直和水品之分。垂直切分优化的是结构,而水平切分优化的是性能。
垂直切分就是把原来的数据表打散,把经常变动的数据(热数据)放在一起,不经常变动的数据(冷数据)单独保存,比如12306的个人信息,上次登录时间算是一个经常变动的数据,而性别,身份证号等基本信息等冷数据就可以放在一个叫base_info的表里。
水平切分可以把我们的数据通过一定的算法指派的到不同的表里,比如保存用户的ID,可以按照对10求余数后的结果,分别把数据保存到user0,user1……user9的表中,这样我们把user表分成了10份,基本可以保证查询效率,比如之前我们的查询总数是100W,但是现在我们只需要面对10W的数据量了!不过聪明的你一定也想到了,这样就不利于聚合函数的发挥了吧?是的,不过简单的办法就是尽量避免此类查询,然后在后台异步进行统计。
参考资料: