sql 規範

一、任何語句使用前經過 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 權限中的必要權限注:權限越大,被攻擊時受到的破壞越大

相關文章
相關標籤/搜索