性能优化之永恒之道(实时sql优化vs业务字段冗余vs离线计算)
在项目中,随着时间的推移,数据量越来越大,程序的某些功能性能也可能会随之下降,那么此时我们不得不需要对之前的功能进行性能优化。如果优化方案不得当,或者说不优雅,那可能将对整个系统产生不可逆的严重影响。
此篇博主为大家分享一些根据自己多年的大数据分布式工作经验总结出优化的方案。
1.实时sql优化:就是将分析出来耗时的sql进行重写、拆分成多次查询后数据重组、去掉sql函数等等;sql能干的事情,程序肯定能干,且程序运行的性能一般情况会快很多,而且web服务器可以部署很多台;优点:可实现快速优化,且性能非常可观;缺点:可能会增加程序的复杂度;
2.业务冗余字段:就是在更新某张数据库业务表时,将其关联表的某些字段冗余进来,以减少某些业务的表关联查询从而达到提升速度效果;优点:实现简单;缺点:此方案只适用于某些比较简单的场景,如果关联的表非常多,且业务需要查询的字段也非常多,此时程序将对其它业务表的业务产生非常严重的侵入【即在更新别的表时候,需要更新该表】,在后期优化过程中,该方案还涉及到初始化数据的问题,另外数据一致性问题堪忧【如多业务保存中途宕机】,所以复杂业务不建议使用该方案。
3.离线计算:就是利用定时任务或者hadoop等到一些离线计算的技术,将数据跑成报表的方式。此方案适用于可支持非实时数据的查看的业务【如报表等等】。优点,可单独部署,不影响主程序,且性能优化效果非常可靠;缺点:数据非实时,且可能出现报表数据和真是数据某些字段不一致的情况,程序复杂度倍增且需要额外服务器支撑。
基于上诉的分析,博主最推荐的还是方案1和方案3这两种处理方案。在优化方案选择的优先级别上:
实时sql优化>离线计算>数据冗余;
注意:在某些特定场景下【如业务非常简单时】,离线计算的优先级别小于数据冗余。
下面举例博主在实际项目中的一些sql优化例子
定位步骤[本次以后台sql导致的性能为例,其它如前端或程序处理导致性能问题不考虑]:
1.使用google浏览器按F12,打开性能差页面,检查耗时的请求
2.根据请求找到访问的controller-service-dbs-->sql
3.sql性能分析【将sql用生产数据库进行运行分析,此次就暂时不讲述使用explain分析的方法,下一篇再详细讲解】
【例1】.根据专区id或者板块id查询该专区或板块的发帖量,在navicat中运行结果27s
SELECT
count(wcc_bbs_reply.id) counts
FROM
wcc_bbs_reply
WHERE
EXISTS (
SELECT
*
FROM
(
SELECT
wcc_bbs.id bbs_id
FROM
wcc_bbs
WHERE
(
wcc_bbs.refer_id = 2
AND wcc_bbs.type = 4
AND wcc_bbs.is_delete = 0
AND wcc_bbs.bbs_state = '2'
)
UNION ALL
SELECT
wcc_bbs.id bbs_id
FROM
wcc_bbs,
wcc_bbs_area
WHERE
(
wcc_bbs.type = 1
AND wcc_bbs.refer_id = wcc_bbs_area.id
AND wcc_bbs_area.bbs_section = 2
AND wcc_bbs.is_delete = 0
AND wcc_bbs.bbs_state = '2'
)
) bbs_extend
WHERE
bbs_extend.bbs_id = wcc_bbs_reply.bbs
)
经过语义分析,该条sql其实是想查询id为2的板块与它下面的专区对应的回帖量总和。那我们是否就可以将该条sql拆分为直接挂在id为2的板块下的帖子回复量和挂在id为2的板块下的专区上的帖子回复量呢?答案是当然可以的,经拆分:
第一条sql(执行时间0.022s):
SELECT
count(wcc_bbs_reply.id) counts
FROM
wcc_bbs_reply , wcc_bbs
WHERE
wcc_bbs.refer_id =2
AND wcc_bbs.type = 4
AND wcc_bbs.is_delete = 0
AND wcc_bbs.bbs_state = '2'
and wcc_bbs.id = wcc_bbs_reply.bbs
第二条sql(执行时间0.17s):
SELECT
count(wcc_bbs_reply.id) counts
FROM
wcc_bbs_reply,
(
SELECT
wcc_bbs.id bbs_id
FROM
wcc_bbs,
wcc_bbs_area
WHERE
(
wcc_bbs.type = 1
AND wcc_bbs.refer_id = wcc_bbs_area.id
AND wcc_bbs_area.bbs_section = 2
AND wcc_bbs.is_delete = 0
AND wcc_bbs.bbs_state = '2'
)
) a
WHERE
a.bbs_id = wcc_bbs_reply.bbs
由此可分析出上诉经过上诉拆分后性能可提高数百倍,小伙伴们是不是特别兴奋;
【例2】.后台帖子管理列表分页查询功能,执行时间为29s,**优化方案-性能逐节衰减方案**
SELECT
*
FROM
(
SELECT
a.id,
'' AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
d.id AS section,
d. NAME AS sectionName,
b.id AS area,
b. NAME AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_bbs_area b ON a.refer_id = b.id
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
LEFT JOIN wcc_bbs_section d ON b.bbs_section = d.id
WHERE
a.type = '1'
UNION ALL
SELECT
a.id,
'' AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
d.id AS section,
d. NAME AS sectionName,
'' AS area,
'' AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
LEFT JOIN wcc_bbs_section d ON a.refer_id = d.id
WHERE
a.type = '4'
UNION ALL
SELECT
a.id,
b.id AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
b.id AS section,
b. NAME AS sectionName,
'' AS area,
'' AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_bbs_circle b ON a.refer_id = b.id
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
WHERE
a.type <> '1'
AND a.type <> '4'
) d
WHERE
1 = 1
AND d.isdelete = 0
ORDER BY
d.bbs_publish_time DESC
LIMIT 10
语义分析:上面这条sql中包含了两个union all,也句是由3条sql结果合并组成;既然功能完全是独立的,我们是否就可以分三次进行查询组合呢,这么复杂的sql,简单化多好,看着也舒服有么有;但是大家别被博主玩坏了,这里可是有排序的,且为分页查询,问题可不是分sql查询这么简单;难道就没办法解决了吗?这就是今天分享的一大SQL经典优化案例,sql性能衰减方案,该方案就是先根据当前页*每页大小(该值名称定义为total,以下将以此名称表示),然后将多个子句按照上诉得出来的total值查询出total条数据,然后进行排序取出当前查询的那页的数据。即:
子句1.【执行时间0.22s】
SELECT
a.id,
'' AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
d.id AS section,
d. NAME AS sectionName,
b.id AS area,
b. NAME AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_bbs_area b ON a.refer_id = b.id
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
LEFT JOIN wcc_bbs_section d ON b.bbs_section = d.id
WHERE
a.type = '1'
AND a.is_delete = 0
ORDER BY
a.bbs_publish_time DESC
LIMIT 10
子句2【执行时间0.20s】
SELECT
a.id,
'' AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
d.id AS section,
d. NAME AS sectionName,
'' AS area,
'' AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
LEFT JOIN wcc_bbs_section d ON a.refer_id = d.id
WHERE
a.type = '4'
AND a.is_delete = 0
ORDER BY
a.bbs_publish_time DESC
LIMIT 10
子句3【执行时间0.21s】
SELECT
a.id,
b.id AS chId,
a.bbs_title,
ifnull(c.member_name, 'admin') AS member_name,
b.id AS section,
b. NAME AS sectionName,
'' AS area,
'' AS areaName,
a.bbs_publish_time,
a.bbs_last_reply_time,
a.bbs_reply_num,
a.bbs_read_num,
a.bbs_agree_num,
a.bbs_state,
a.bbs_lock,
a.bbs_elite,
a.bbs_label_text,
a.bbs_top,
a.is_delete AS isdelete,
a.bbs_property AS proerty,
a.type AS type,
a.bbs_close AS CLOSE,
a.plate_bbs_top AS plateTop,
a.pcidx_bbs_show AS pcidxBbsShow
FROM
wcc_bbs a
LEFT JOIN wcc_bbs_circle b ON a.refer_id = b.id
LEFT JOIN wcc_ch_member c ON a.ch_member = c.id
WHERE
a.type <> '1'
AND a.type <> '4'
AND a.is_delete = 0
ORDER BY
a.bbs_publish_time DESC
LIMIT 10
由此可以看出上面三条sql查询出来耗时还不到一秒,组装是程序速度是非常快的,几乎时间可以忽略。但随着页数的增大,limit 后面的10就成当前页的大小的倍数增大,所以当前页越大时,需要查询运算的数据就会越来越多,性能就会进行衰减,且此方法会将数据load到内存占用内存空间,使用者需权衡使用。一个很好的解决到了一定页面后,性能衰减到客户不能承受的时间差时,我们可以判断页数到达多少页时,使用原来一条sql的查询,保证功能可使用。此方案适用于列表在业务上查询使用新数据多,老数据几乎不用的场景。
【例3】.会员明细列表查询,会统计很多用户信息,如积分等等;**经典left join查询优化方案**
SELECT
m.id,
m.member_name,
m.phone_no,
CASE
WHEN m.sex = '1' THEN
'男'
WHEN m.sex = '2' THEN
'女'
END sex,
m.email,
CASE
WHEN m.member_level = '0' THEN
'游客'
WHEN m.member_level = '1' THEN
'普卡'
WHEN m.member_level = '2' THEN
'银卡'
WHEN m.member_level = '3' THEN
'金卡'
END member_level,
m.registration_time,
m.expiration_time,
s. NAME,
d.dealer_name,
d.dealer_no,
n.vin_code,
n.carType,
j.canUseScore,
j1.usedScore,
j2.sumScore
FROM
wcc_ch_member m
LEFT JOIN (
SELECT
v.wcc_member_info,
GROUP_CONCAT(
t.vehicle_type_name SEPARATOR ';'
) AS carType,
GROUP_CONCAT("'", v.vin_code, "'") AS vin_code
FROM
wcc_ch_vehicle v,
wcc_vehicle_type t
WHERE
v.wcc_vehicle_type = t.id
AND (v. STATUS = 1 OR v. STATUS = 3)
GROUP BY
v.wcc_member_info
) n ON n.wcc_member_info = m.id
LEFT JOIN wcc_friend h ON h.open_id = m.open_id
LEFT JOIN wcc_sale_assist s ON h.said = s.id
LEFT JOIN wcc_ch_dealer d ON s.dealer = d.id
LEFT JOIN (
SELECT
wcc_member_info,
sum(can_use_score) canUseScore
FROM
wcc_ch_integral_detail
WHERE
isadd = 1
AND can_use_score > 0
AND (
expiration_Date IS NULL
OR expiration_date >= '2018-07-05 14:00:34'
)
GROUP BY
wcc_member_info
) j ON m.id = j.wcc_member_info
LEFT JOIN (
SELECT
wcc_member_info,
sum(score - return_score) usedScore
FROM
wcc_ch_integral_detail
WHERE
isadd = '0'
GROUP BY
wcc_member_info
) j1 ON m.id = j1.wcc_member_info
LEFT JOIN (
SELECT
wcc_member_info,
sum(score) sumScore
FROM
wcc_ch_integral_detail
WHERE
isadd = '1'
GROUP BY
wcc_member_info
) j2 ON m.id = j2.wcc_member_info
WHERE
1 = 1
LIMIT 0,
10
分析,此sql中全是left子句,线上功能已经处于不可用状态.查询表单如下:
页面查询表单分析,子句中只有一个vin码是查询条件,vin对应唯一的一辆车,一辆车又对应唯一的一个车主会员,于是我们可以将所有子句拆分出来分批查询组装后返回;思路如下,在没有vin查询时候,我们根据上面条件单表查询wcc_ch_member得出当前页的10条数据的id集合,然后根据这10条数据使用memberid in(......)的方式分批次去查询后面的统计字段和其他子句的字段进行组装。经分析,所有子句查询都在0.01秒左右,且wcc_ch_member在单表查询是也是0.02秒左右,由此可推算出第一种情况查询优化效果提升千万倍性能。那么在有vin码条件的情况下呢,我们首先可以根据vin码查询出member表的会员id,然后根据id再去按照上面的方法查询对应的统计,分析出来只多了一步member查询,性能上和上诉无vin码查询是效果几乎无变化,且可能更快。
最后总结一下,所有子句的查询均可以分批次查询来优化,所有left join类型的查询其实均可转为单表查询。当然,sql业务拆分级的优化方案不止上诉几种,这里博主只是抛砖头引璞玉。
作者:em_aaron
链接:https://my.oschina.net/u/2371923/blog/1841088?utm_source=tuicool&utm_medium=referral
性能优化之永恒之道(实时sql优化vs业务字段冗余vs离线计算)的更多相关文章
- oracle性能优化(项目中的一个sql优化的简单记录)
在项目中,写的sql主要以查询为主,但是数据量一大,就会突出sql性能优化的重要性.其实在数据量2000W以内,可以考虑索引,但超过2000W了,就要考虑分库分表这些了.本文主要记录在实际项目中,一个 ...
- mysql系列十一、mysql优化笔记:表设计、sql优化、配置优化
可以从这些方面进行优化: 数据库(表)设计合理 SQL语句优化 数据库配置优化 系统层.硬件层优化 数据库设计 关系数据库三范式 1NF:字段不可分; 2NF:有主键,非主键字段依赖主键; 3NF:非 ...
- 数据库sql优化总结之5--数据库SQL优化大总结
数据库SQL优化大总结 小编最近几天一直未出新技术点,是因为小编在忙着总结整理数据库的一些优化方案,特此奉上,优化总结较多,建议分段去消化,一口吃不成pang(胖)纸 一.百万级数据库优化方案 1.对 ...
- MySQL 数据库性能优化之SQL优化【转】
优化目标 减少 IO 次数IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑, ...
- MySQL性能优化方法四:SQL优化
原文链接:http://isky000.com/database/mysql-performance-tuning-sql 注:这篇文章是以 MySQL 为背景,很多内容同时适用于其他关系型数据库,需 ...
- MySQL数据库性能优化:表、索引、SQL等
一.MySQL 数据库性能优化之SQL优化 注:这篇文章是以 MySQL 为背景,很多内容同时适用于其他关系型数据库,需要有一些索引知识为基础 优化目标 减少 IO 次数IO永远是数据库最容易瓶颈的地 ...
- 霜皮剥落紫龙鳞,下里巴人再谈数据库SQL优化,索引(一级/二级/聚簇/非聚簇)原理
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_206 举凡后端面试,面试官不言数据库则已,言则必称SQL优化,说起SQL优化,网络上各种"指南"和" ...
- oracle sql优化笔记
oracle优化一般分为:1.sql优化(现在oracle都会根据sql语句先进行必要的优化处理,这种应该用户不大了,但是像关联和嵌套查询肯定是和影响性能的) A.oracle的sql语句的条件是从右 ...
- SQL优化的四个方面,缓存,表结构,索引,SQL语句
一,缓存 数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作.而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级.所 ...
随机推荐
- python scrapy 把cookie并转化为字典的形式
在用scrapy设置cookie的时候,需要从网页上对应的页面把cookie字段复制下来,并转化为字典的形式,下面代码是对cookie的转化过程 # -*- coding: utf-8 -*- cla ...
- 简短而有效的python queue队列解释
Queue.qsize() 返回队列的大小 Queue.empty() 如果队列为空,返回True,反之False Queue.full() 如果队列满了,返回True,反之False Queue ...
- Bug03_SSM整合_mybatis result maps collection already contains value...
这个问题是在映射文件,但是这个映射文件是自动生成的. 所以想起来可能是在自动生成映射文件,pojo类时操作有问题.第一次运行时,出现错误,没有删除已经生成的文件就直接运行了第二次. 解决办法:删除旧的 ...
- 一个简单的 IDA f5插件问题分析
有人提出问题,以下汇编f5结果缺失代码: .text:00000C18 Java_com_a_b_c .text:00000C18 PUSH {R3,LR} .text:00000C1A CMP R2 ...
- SVD分解求解旋转矩阵
1.设是两组Rd空间的点集,可根据这两个点集计算它们之间的旋转平移信息. 2.设R为不变量,对T求导得: 令 则 将(4)带入(1)得: 令 则 (相当于对原来点集做减中心点预处理,再求旋转量) 3. ...
- animate.css动画
添加类名的时间不要只添加动画的类名,也要加上animated,使用的时间可以把自己需要的效果复制出来
- P1220 关路灯 (区间dp)
题目链接:传送门 题目大意: 总共有N盏灯,老张从点C(1 ≤ C ≤ N)开始关灯(关灯不需要等待时间,C点的灯直接关掉),与此同时灯开始烧电(已知功率Pi). 老张每次可以往左走关最近的灯或者往右 ...
- ACM-ICPC 2018 沈阳赛区网络预赛-D:Made In Heaven(K短路+A*模板)
Made In Heaven One day in the jail, F·F invites Jolyne Kujo (JOJO in brief) to play tennis with her. ...
- 2018.4.2 flask web
from flask import Flask,request from flask import jsonify from flask import render_template app = Fl ...
- PHP黑魔法(该篇文章转自:http://www.91ri.org/12634.html 目的是作为自己的笔记方便查找)
那些年我们学过的PHP黑魔法 作者:Matrix_ling 序 这里必须得说一下==和===这俩货的重要性.==是比较运算,它不会去检查条件式的表达式的类型===是恒等,它会检查查表达式的值与类型是否 ...