老司機大型車禍現場

就在本週,應該是在本週二,小編翻車啦~~~算法

以前有關注個人同窗應該知道,小編在國慶節寫了一隻爬蟲,來抓取本身的各個平臺博客的訪問量等一些數據,而且後面簡單作了個報表,主要是靠 SQL 來統計數據。sql

這隻爬蟲小編部署到 Linux 服務器上之後,設置了整點定時抓取數據也沒管過,卻是剛上線那段時間常常去報表平臺看看統計報表。數據庫

想了解事情原由的同窗能夠看一下這兩篇文章《Python 簡易爬蟲實戰》《小白 Python 爬蟲部署 Linux》瀏覽器

而後,就在小編偶然上去看報表的時候,發現已經不一樣尋常的事情。bash

我靠,咋點了半天沒反應。。。服務器

感受時間過了半個世紀,報表才渲染出來。ide

確定哪裏不對,小編趕忙用 Navicat 執行了一下報表的 SQL ,結果:測試

對的,你沒看錯,這句話執行了 20s+ ,小編當時的內心感受有一萬隻羊駝奔騰而過。優化

有沒有搞錯,數據攏共 2k 多條,執行一下要 20s+ ,是在開玩笑麼,小編當時都懷疑本身是否是用了一臺假數據庫。ui

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 個子查詢,整體的運算量,好吧,小編認可確實有點大了。。。

優化的時候,主體的思路是下降當前查詢的運算量,那解決方案就很好想了,要麼是在每次寫入數據的時候進行運算,至關因而總體的運算量分佈到每次寫入了,要麼是添加一個定時任務,讓大量的運算天天都只運行一次,後續的查詢直接查詢運算結果。

若是個人文章對您有幫助,請掃碼關注下做者的公衆號:獲取最新干貨推送:)
相關文章
相關標籤/搜索