一、任何語句使用前經過 EXPLAIN 查看執行計劃是否用到索引
explain select ...;
-- 問題語句:(去除嵌套查詢中無心義排序)
EXPLAIN SELECT COUNT(*) AS num
FROM ( SELECT b.id,
FROM ( SELECT id,
WHERE yczt = 0 AND hfbz = 0 AND (bm = '410045' OR bmr = '410045')
ORDER BY lysj DESC) b
GROUP BY b.dhb_id) c
INNER JOIN dhb a
ON a.id = c.dhb_id
LEFT JOIN ydserver.gs d
ON c.bm = d.bm
LEFT JOIN ydserver.gs e
ON c.bmr = e.bm
WHERE 1
ORDER BY c.lysj DESC, c.dhb_id;redis
二、不要從明細表查統計結果,按期統計插入到彙總表
-- 問題語句:
SELECT COUNT(txm) AS zs,SUM(ifqs) AS qs
FROM wdcx.yd_scan03
WHERE dd = 450001 AND rksj >= '2014-05-30 00:00:00'
AND rksj <= '2014-05-30 23:59:59' AND (ifdj = 1 OR ifff = 1);
# Query_time: 18.229131 Lock_time: 0.000474 Rows_sent: 0 Rows_examined: 180139
注:從明細表查詢時檢索記錄數爲 180139,從彙總表查詢時檢索記錄數爲 1,須要改成從彙總表查詢數據庫
三、禁止使用SELECT *,必須指定字段名稱.沒法完成索引覆蓋掃描這類優化,還會給服務器帶來額外的I/O、內存和CPU的消耗
SELECT * FROM cust_txm
WHERE txm = '3100042251575'
CREATE TABLE cust_txm;
注:所有返回時,不少字段用不到,另數值條件不要加引號緩存
四、明細統計時,只統計編碼,不要關聯名稱等冗餘字段
-- 問題語句:
SELECT CURPLACE AS xcdd,mc,SUM(zczs - zcsum - xczs + xcsum) AS sjzcsm,
FROM (SELECT aa.LINEID,
aa.mc
FROM (SELECT DISTINCT a.LINEID,
d.mc --名稱
FROM tst_car_line_info a
LEFT JOIN tst_place_pre_nex b ON a.LINEID = b.LINEID
LEFT JOIN tst_down_up_shipment c ON a.LINEID = c.LINEID
LEFT JOIN ydserver.gs d ON b.CURPLACE = d.bm AND d.lb = 3
LEFT JOIN ydserver.county e ON d.szd = e.CountyID
LEFT JOIN ydserver.city f ON e.CityID = f.CityID
WHERE (b.CURPLACE = '0' OR 0 = '0')
AND IFNULL(a.FACTCAR_D, a.FACTLOADOR_D) >='2014-06-14'
AND IFNULL(a.FACTCAR_D, a.FACTLOADOR_D) <='2014-06-15') aa
GROUP BY xcdd;
注:名稱顯示可查詢全局 hashtable(寫300萬/秒 讀1200萬/秒)
-- 其餘 KV 工具:
memcached(讀寫 8萬/秒,多線程更快,適合小數據)
redis (讀寫10萬/秒,單線程,可多進程,適合大數據和複雜數據結構)服務器
五、聯合查詢時,每一個表必須加別名,關聯字段必須是索引(最好是主鍵),where條件用以過濾主表
-- 問題語句:
SELECT a.*, b.kilometre
FROM car.car_line a
LEFT JOIN car.car_roadline b
ON a.roadlineid = b.roadlineid AND b.del_flag = 0
WHERE lineid = '31001383713' AND a.del_flag = 0;數據結構
六、語句中避免子查詢
--問題語句
SELECT t1.*
FROM ydserver.ic_site_bound t1
WHERE DEV_ID IN (
SELECT dev_id
FROM car.tb_crd t2
WHERE t2.scan_time >= '2014-06-14 00:00:00'
AND t2.scan_time <= '2014-06-17 00:00:00'
);
--改寫
SELECT t1.*
FROM ydserver.ic_site_bound t1, (
SELECT DISTINCT dev_id
FROM car.tb_crd
WHERE scan_time >= '2014-06-14 00:00:00'
AND scan_time <= '2014-06-17 00:00:00'
) t2
where t2.dev_id=t1.DEV_ID;多線程
若是 IN 列表太多必須改成關聯的方式 , 且經過主鍵關聯memcached
七、大表 join 用臨時表代替 (create temporary table)
SELECT *
FROM (SELECT ROW_NUMBER () OVER (ORDER BY reverttime DESC) AS ROWNUM,
R.cardid,
R.moduleid,
cardtheme,
cardperson,
T.revertcontent,
T.revertperson,
T.reverttime,
W.cardnum,
H.Hid,
G.Gid
FROM Card R
LEFT JOIN
(SELECT revertid,
cardid,
revertcontent,
revertperson,
reverttime
FROM Reverts Y
WHERE NOT EXISTS
(SELECT 1
FROM Reverts
WHERE cardid = Y.cardid
AND revertid > Y.revertid)) T
ON R.cardid = T.cardid
LEFT JOIN ( SELECT cardid, COUNT (*) AS cardnum
FROM Reverts
GROUP BY cardid) W
ON R.cardid = W.cardid
LEFT JOIN (SELECT id AS Hid, username FROM UserInfoTable) H
ON R.cardperson = H.username
LEFT JOIN (SELECT id AS Gid, username FROM UserInfoTable) G
ON T.revertperson = G.username
WHERE R.moduleid = CAST (@moduleid as nvarchar(50))) as TEMPRESULT
where rownum between str((@currentpage-1)*@pagesize)+1 and str(@currentpage*@pagesize)
--使用臨時表保存如下結果集數據
SELECT *
FROM (SELECT ROW_NUMBER () OVER (ORDER BY reverttime DESC) AS ROWNUM,
R.cardid,
R.moduleid,
cardtheme,
cardperson,
T.revertcontent,
T.revertperson,
T.reverttime,
W.cardnum,
H.Hid,
G.Gid
FROM Card R
LEFT JOIN
(SELECT revertid,
cardid,
revertcontent,
revertperson,
reverttime
FROM Reverts Y
WHERE NOT EXISTS
(SELECT 1
FROM Reverts
WHERE cardid = Y.cardid
AND revertid > Y.revertid)) T
ON R.cardid = T.cardid
WHERE R.moduleid = CAST (@moduleid as nvarchar(50))) as TEMPRESULT
where rownum between str((@currentpage-1)*@pagesize)+1 and str(@currentpage*@pagesize);
而後,再和其餘表進行left join 函數
9.字段設計
(1)儘量使用更小的數據類型,如 TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。
更小的數據類型一般更快,由於他們佔用更少的磁盤、內存和CPU緩存,而且處理時須要的CPU週期更少。
例如,整型比字符操做代價更低,由於字符集和校對規則使字符比整型比較更復雜。工具
(2)相同屬性對應的數據類型,如字符型,數值型不能混合使用,依賴後期轉換
有可能不走索引
varchar(5)和varchar(200)存儲'hello'的空間開銷是同樣的。可是,更長的列在使用內存臨時表進行排序或操做時,會消耗更多的內存。性能
(3)相同字段不一樣表中的類型和長度要一致
導數可能報錯
(4)字段名稱不能使用關鍵字
(5)不要指定字段級編碼,建議全庫統一
CREATE TABLE `delivery`(
`user_id` VARCHAR(20) CHARACTER SET utf8,
`order_id` INT(11),
`order_bn` VARCHAR(32) CHARACTER SET utf8,
`delivery_bn` VARCHAR(20) CHARACTER SET utf8,
`is_scan` enum('1', '0') CHARACTER SET utf8,
`scan_time` datetime ,
`create_time` datetime '生成時間'
);
(6)默認值要規範,例如日期不要使用 0000-00-00
CREATE TABLE yd_cas_org
(
orgid bigint(10) NOT NULL,
orgcode VARCHAR(32) NOT NULL,
status VARCHAR(255) DEFAULT 'running',
area VARCHAR(30) DEFAULT NULL,
lastupdatetime timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
);
注:特有默認值在 ETL 時會致使異常
(7)事務相關記錄保留時間戳,建議只增不改;在必須對記錄進行修改的時候,保留更改時間戳
例如:
ctime datetime NOT NULL COMMENT '建立時間',
utime timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間'
10. 索引
(1)通常狀況下,一次查詢只會用到一個索引
(2)每一個表索引越少越好
(3)創建組合索引時,WHERE 條件中用到等於的字段放前邊,用到範圍的字段放後邊。多個範圍條件創建的索引沒法同時使用
若是查詢中有某個列的範圍查詢,則其右邊全部列都沒法使用索引優化查找,
例如:where last_name='Smith' and first_name like 'J%' and bob='1976-12-23',這個查詢只能使用索引的錢兩列,由於like是一個範圍條件。
(4)刪除重複字段的索引,減小 DML IO
-- 問題語句:
CREATE TABLE temp_car_roadline (
I D int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '線路的id',
RoadLineID char(9) NOT NULL COMMENT '線路編碼',
LineType varchar(11) NOT NULL COMMENT '線路類別',
qty_month int(11) DEFAULT '30' COMMENT '月跑趟數',
PRIMARY KEY (ID),
KEY RoadLineID (RoadLineID),
KEY RoadLineID2 (RoadLineID,build_date)
)
注:索引過多影響操做效率,重複索引可能致使執行計劃異常
索引(RoadLineID,build_date)能夠看成索引(RoadLineID)使用
(6)索引中重複的記錄數越少,效率越高,效率最高的是主鍵
注:若是同一記錄超過50%,全表掃描按期 analyze table 收集統計信息和直方圖
選擇性高的列放到組合索引的前面。
(7)索引字段最好不要存在 NULL,NULL可用 0 替代,建議把默認值設置爲 0,若是能夠加 not null 或者 unique 的最好加上
由於可爲NULL的列使得索引、索引統計和值比較都更復雜。
(8)組合索引能夠只使用第一個,或者前兩個,或者前幾個,不能從第二個開始用,也不能跳着使用
注:索引使用從前綴開始,多字段索引到between或者<,>等之後字段不會使用索引,排序最好在索引中實現
11.查詢條件
(1)SQL 語句的 WHERE 條件避免使用無效條件、無效括號
-- 問題語句:
SELECT ydserver.gs.BM, ydserver.gs.SHI, ydserver.gs.MC
FROM ydserver.gs
WHERE (1=1)
AND ( (ydserver.gs.SHI LIKE '%') AND (ydserver.gs.sheng LIKE '%') )
AND ( ydserver.gs.BM <> 0 )
AND ( ydserver.gs.sjgs <> 0 )
ORDER BY ydserver.gs.BM ASC;
注:示例語句中使用了無效條件、無效括號,對性能有極大影響
(2)SQL語句中不要加用不到的排序
作統計不必用加"order by ..."
(3)WHERE 條件中 最好不要用 IN 和 LIKE
-- 問題語句:
SELECT yd_cost.yjsm_czz.*
FROM yd_cost.yjsm_czz, gs
WHERE (yd_cost.yjsm_czz.xjdd = gs.bm)
AND (yd_cost.yjsm_czz.dd = '8.30001000000000000e+005')
AND (yd_cost.yjsm_czz.sj >= '2014-06-15 14:00:00')
AND (yd_cost.yjsm_czz.sj <= '2014-06-15 23:59:59')
AND (cast(yd_cost.yjsm_czz.xjdd AS char(19)) LIKE '410088')
AND (cast(gs.sheng AS char(6)) LIKE '%')
AND (cast(gs.shi AS char(6)) LIKE '%');
注:可以使用 exists 代替 in, 使用 = 代替 like,即便使用like也是儘量將「%」放到字符串後面,例如: like 'car%'
避免使用 LIKE
-- 問題語句:
SELECT *
FROM t
WHERE lrsj LIKE "2012-09-23%";
注:須要尋找 LIKE 的替代方案,如 SELECT * FROM t WHERE lrsj BETWEEN '2012-09-23 00:00:00' AND '2012-09-23 23:59:59'
(4)索引相關字段不要使用函數或者進行運算,如 field1 + 1 = field二、ADDDATE(field1,…、CAST
-- 問題語句:
SELECT t1.CarLicNum, t1.RoadLineName, t2.LeaveTime
FROM car_line t1
LEFT JOIN car_roadlinedetail t2
ON t2.roadlineid = t1.RoadLineID AND t2.del_flag = 0
WHERE CAST(CONCAT(t1.Startdate, ' ', t1.StartTime) AS datetime) BETWEEN '2014-06-10 10:30:00'
AND '2014-06-18 10:30:00'
AND t1.del_flag = 0
ORDER BY t1.LineID, t2.PassNo;
注:大多數字段使用函數不會使用索引,除非加函數索引
因此,始終將索引列單獨放在比較符號的一側。
(5)禁止字段格式轉換,如 SELECT x FROM GS WHERE BM=200000,數值兩邊不要加引號
-- 問題語句:
SELECT cz_pzxx.cp,
cz_pzxx.pzbh,
cz_pzxx.pz,
cz_pzxx.pic_path
FROM cz_pzxx
WHERE (cz_pzxx.lrdd = '3.10000000000000000e+005') AND (cz_pzxx.scbz = 0);
注:要區分數值、日期和字符串,科學計數法更要慎重使用
12.存儲過程
(1)在存儲過程的關鍵步驟開始和結束都要記錄信息到日誌表,用於監控和調試
(2)過程避免每條語句提交
-- 正確語句:
START TRANSACTION;
INSERT INTO t(datetime, UID, content, TYPE) VALUES ('0', 'userid_0', 'content_0', 0),('1', 'userid_1', 'content_1', 1);
INSERT INTO t(datetime, UID, content, TYPE) VALUES ('2', 'userid_2', 'content_2', 2),('3', 'userid_3', 'content_3', 3);
COMMIT;
注:經過事務提交能夠提升大數據操做效率,同時有序插入、合併插入也能夠大幅提升數據庫效率
13.查詢技巧
-- 問題語句:
SELECT t.*
FROM (SELECT LineID, SealStatus, count(*) AS num
FROM car_line_dtl
WHERE LineID = '31001367902' AND del_flag = 0
UNION
--- 省略 70 KByte
SELECT LineID, SealStatus, count(*) AS num
FROM car_line_dtl
WHERE LineID = '31001370528' AND del_flag = 0
GROUP BY LineID, SealStatus) t;
注:可經過分批查詢或者使用臨時表方式下降查詢語句大小
(2)
WHERE 多個 OR 條件不走一個索引時可經過 UNION
-- 問題語句:
SELECT *
FROM t
WHERE bm1 = 953016 OR bm2 = 953016
注:一次查詢通常走一個索引,可經過 UNION ALL 優化,如 SELECT * FROM t WHERE bm1 = 953016 UNION ALL SELECT * FROM t WHERE bm2 = 953016
14. 權限控制PHP 鏈接 MYSQL 的用戶只分配對應庫 SIUD 權限中的必要權限注:權限越大,被攻擊時受到的破壞越大