性能優化之永恆之道(1)(實時sql優化vs業務字段冗餘vs離線計算)

    在項目中,隨着時間的推移,數據量愈來愈大,程序的某些功能性能也可能會隨之降低,那麼此時咱們不得不須要對以前的功能進行性能優化。若是優化方案不得當,或者說不優雅,那可能將對整個系統產生不可逆的嚴重影響。前端

    此篇博主爲你們分享一些根據本身多年的大數據分佈式工做經驗總結出優化的方案。web

    1.實時sql優化:就是將分析出來耗時的sql進行重寫、拆分紅屢次查詢後數據重組、去掉sql函數等等;sql能幹的事情,程序確定能幹,且程序運行的性能通常狀況會快不少,並且web服務器能夠部署不少臺;優勢:可實現快速優化,且性能很是可觀;缺點:可能會增長程序的複雜度;sql

    2.業務冗餘字段:就是在更新某張數據庫業務表時,將其關聯表的某些字段冗餘進來,以減小某些業務的表關聯查詢從而達到提高速度效果;優勢:實現簡單;缺點:此方案只適用於某些比較簡單的場景,若是關聯的表很是多,且業務須要查詢的字段也很是多,此時程序將對其它業務表的業務產生很是嚴重的侵入【即在更新別的表時候,須要更新該表】,在後期優化過程當中,該方案還涉及到初始化數據的問題,另外數據一致性問題堪憂【如多業務保存中途宕機】,因此複雜業務不建議使用該方案。數據庫

    3.離線計算:就是利用定時任務或者hadoop等到一些離線計算的技術,將數據跑成報表的方式。此方案適用於可支持非實時數據的查看的業務【如報表等等】。優勢,可單獨部署,不影響主程序,且性能優化效果很是可靠;缺點:數據非實時,且可能出現報表數據和真是數據某些字段不一致的狀況,程序複雜度倍增且須要額外服務器支撐。瀏覽器

    基於上訴的分析,博主最推薦的仍是方案1和方案3這兩種處理方案。在優化方案選擇的優先級別上:性能優化

實時sql優化>離線計算>數據冗餘;服務器

    注意:在某些特定場景下【如業務很是簡單時】,離線計算的優先級別小於數據冗餘。分佈式

   

下面舉例博主在實際項目中的一些sql優化例子函數

定位步驟[本次之後臺sql致使的性能爲例,其它如前端或程序處理致使性能問題不考慮]:oop

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業務拆分級的優化方案不止上訴幾種,這裏博主只是拋磚頭引璞玉。若是你對業務級拆分性能優化方案或者其它服務器性能優化方案感興趣的話,請點贊博主,並歡迎同博主交流。

相關文章
相關標籤/搜索