就在本周,应该是在本周二,小编翻车啦~~~算法
以前有关注个人同窗应该知道,小编在国庆节写了一只爬虫,来抓取本身的各个平台博客的访问量等一些数据,而且后面简单作了个报表,主要是靠 SQL 来统计数据。sql
这只爬虫小编部署到 Linux 服务器上之后,设置了整点定时抓取数据也没管过,却是刚上线那段时间常常去报表平台看看统计报表。数据库
想了解事情原由的同窗能够看一下这两篇文章《Python 简易爬虫实战》 和 《小白 Python 爬虫部署 Linux》 。浏览器
而后,就在小编偶然上去看报表的时候,发现已经不一样寻常的事情。服务器
我靠,咋点了半天没反应。。。ide
感受时间过了半个世纪,报表才渲染出来。测试
确定哪里不对,小编赶忙用 Navicat 执行了一下报表的 SQL ,结果:优化
对的,你没看错,这句话执行了 20s+ ,小编当时的内心感受有一万只羊驼奔腾而过。设计
有没有搞错,数据拢共 2k 多条,执行一下要 20s+ ,是在开玩笑么,小编当时都怀疑本身是否是用了一台假数据库。3d
emmmmmmmmmm,顺便介绍一下数据库配置,使用的是某云服务的 MySQL 库,硬件配置为 1H1G(1核1G)。
冷静下,深呼吸两个,小编不信邪,正好手上有一台 2H8G 的服务器,赶忙搭一个 Mysql 试一下,某云提供的必定是假数据库。
结果,emmmmmmmmmmm,仍是直接给各位同窗看吧。
好吧,如今要正式这个错误。。。
不过服务器的核心数加了一倍(一个)时间数少了一半多,那理论上讲,若是小编用的是一台物理机,若是这台物理机有大几十核的 CPU ,就不会有问题啊~~~
果真,仍是贫穷惹的祸。。。。
告辞。
固然,本文不会就这么结束:)
如今,已经发现错误了,接下来就是尤其关键的一步了,解决这个问题。
程序猿解决问题是有一个万能的模版,先用这个模版套一下:
emmmmmmmmmm,提问题的人是小编本身,因此,小编不能解决掉本身,那么,这就不是个问题,因此也就不须要解决了。。。。。。。。
不行,这个仍是太影响使用体验,仍是来正经的分析下这个问题。
首先看下数据库的表设计:
咳咳。
就这么一张表,当时比较懒,爬虫每次抓取的都是当时的截面数据,统计报表的 sql 须要动态的去算出天天的增量数据。具体算法是使用当天的最大值减去前一天的最大值,再进行分类统计。
看下小编的 sql 吧:
SELECT a.read_num - ( SELECT b.read_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS read_num, a.fans_num - ( SELECT b.fans_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS fans_num , a.like_num - ( SELECT b.like_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS like_num, ( SELECT b.rank_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) - a.rank_num AS rank_num , a.create_date,a.plantform FROM (SELECT * FROM spider_data ORDER BY create_date DESC LIMIT 1000000000000000) a GROUP BY DATE_FORMAT(a.create_date, '%Y-%m-%d'), a.plantform ORDER BY a.create_date DESC;
稍微长了一丢丢,不过从这个 sql 上,已经能看到明显的问题了,在作查询的时候,使用了大量的子查询。
至于为何要这么写,固然是由于懒咯~~~
前面的爬虫已经将截面数据爬取到了,后面作统计固然是一句话搞定了。
结果就是这偷懒的一句话,酿造了今天的惨剧。
小编脑子里瞬间出现一种解决方案,在爬虫每次爬取数据的时候,就作一次结果数据处理,新建另一张结果表,每次爬取完数据后,同时计算出结果数据,直接存入结果数据表中。
统计数据的报表直接取结果表中的数据,确定快的飞起~~~
啥子,还要我改以前的爬虫代码,不知道程序猿都是懒癌晚期么,开神马玩笑!!!
瞬间大脑就将第一个方案推翻了,第二个折中的方案也悄然浮上心头。
天天凌晨作一个定时任务,定时的统计前一天的数据,这样的修改代价是最小的,可是就是天天只能看到前一天的统计数据。
这个方法好,只须要加一个定时任务就能搞定,可是就是有点改变目前现有的需求,好吧,仍是能够接受的,为了解决这个问题,只能顺便解决一点本身了。
定时任务有好多种作法,一种是直接作在 Mysql 数据库上,还能够用 Python 写成脚本,在 Linux 上设置定时任务去调用对应的 Python 脚本。
小编这么懒的人,怎么可能去作 Linux 的定时任务,固然是直接使用 Mysql 的定时任务。
顺手查了一下百度,自 MySQL5.1.6 起,MySQL 增长了一个很是有特点的功能-事件调度器(Event Scheduler)。
意思就是小编目前使用的 Mysql 版本是 5.7 ,确定是有定时任务功能。
首先在建立定时任务前须要先建立一个存储过程,而后给这个存储过程设置定时执行。
CREATE DEFINER=`root`@`%` PROCEDURE `TimerBlogData`() BEGIN INSERT INTO result_data (read_num, fans_num, like_num, rank_num, create_date, plantform) ( SELECT a.read_num - ( SELECT b.read_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS read_num, a.fans_num - ( SELECT b.fans_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS fans_num , a.like_num - ( SELECT b.like_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) AS like_num, ( SELECT b.rank_num FROM spider_data b WHERE b.plantform = a.plantform AND DATE_FORMAT(b.create_date, '%Y-%m-%d') = date_sub(DATE_FORMAT(a.create_date, '%Y-%m-%d'), INTERVAL 1 DAY) ORDER BY b.create_date DESC LIMIT 1 ) - a.rank_num AS rank_num , a.create_date,a.plantform FROM (SELECT * FROM spider_data ORDER BY create_date DESC LIMIT 1000000000000000) a WHERE DATE_FORMAT(a.create_date, '%Y-%m-%d') = DATE_FORMAT(date_sub(now(), interval 1 day), '%Y-%m-%d') GROUP BY DATE_FORMAT(a.create_date, '%Y-%m-%d'), a.plantform ORDER BY a.create_date DESC ); END
写好了可使用 CALL TimerBlogData()
执行一下,看下数据是否能够正常写入,测试成功后就能够建立 MySQL 的定时任务了。
在 Mysql 上建立定时任务要先看一下当前是否已开启事件调度器,可使用如下 SQL 进行查看:
SHOW VARIABLES LIKE 'event_scheduler'; SELECT @@event_scheduler;
若是看到结果显示 ON
,则表明已经开启,若是看到的是 OFF
,则未开启,未开启的数据库须要先开启这个功能。
此功能能够经过修改数据库配置 my.cnf 文件来完成,在配置中添加 event_scheduler = 1
,由于小编使用的是云服务,直接在数据库后台配置中完成修改便可。
接下来建立定时任务,若是使用 Navicat 图形化界面建立会比较简单,小编并未使用过,下面仍是直接贴代码:
CREATE EVENT timer_blog_data ON SCHEDULE EVERY 1 DAY STARTS DATE_ADD(DATE_ADD(CURDATE(), INTERVAL 1 DAY), INTERVAL 1 HOUR) DO CALL TimerBlogData()
设置定时任务为天天凌晨 1 点执行,任务名为:timer_blog_data 。
运行完成后,打开 Navicat 查看事件,右键选择设计事件:
能够看到定时任务设置成功。
修改下统计报表的程序,讲取数规则从原来的 SQL 改成从结果表直接查询,部署服务器,重启。打开浏览器从新尝试。果真又成了秒开。
问题是解决了,仍是要分析一下本次问题的。
从小编的 sql 中,能够看到每次查询,每一条数据的取出,都须要在子查询中从新检索整张表,并选择出对应的数据进行计算,而每次查询,都有 2k 多条数据会参加总体查询,每一条数据的查询,都包含了 4 个子查询,整体的运算量,好吧,小编认可确实有点大了。。。
优化的时候,主体的思路是下降当前查询的运算量,那解决方案就很好想了,要么是在每次写入数据的时候进行运算,至关因而总体的运算量分布到每次写入了,要么是添加一个定时任务,让大量的运算天天都只运行一次,后续的查询直接查询运算结果。