• sql 规范


    1、任何语句使用前通过 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;

    2、不要从明细表查统计结果,定期统计插入到汇总表
    -- 问题语句:
      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,需要改为从汇总表查询


    3、禁止使用SELECT *,必须指定字段名称.无法完成索引覆盖扫描这类优化,还会给服务器带来额外的I/O、内存和CPU的消耗
      SELECT *  FROM cust_txm
       WHERE txm = '3100042251575'
           CREATE TABLE cust_txm;
          注:全部返回时,很多字段用不到,另数值条件不要加引号

    4、明细统计时,只统计编码,不要关联名称等冗余字段
    -- 问题语句:
    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万/秒,单线程,可多进程,适合大数据和复杂数据结构)

    5、联合查询时,每个表必须加别名,关联字段必须是索引(最好是主键),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;

    6、语句中避免子查询
    --问题语句
      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 列表太多必须改为关联的方式 , 且通过主键关联


    7、大表 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 = field2、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 权限中的必要权限
    注:权限越大,被攻击时受到的破坏越大

  • 相关阅读:
    【Intellij Idea】设置JDK
    MarkDown换行
    Git 查看/修改用户名、邮箱
    JavaScript对象
    Javascript事件
    第十次会议
    第九次会议
    详细设计文档
    第八次会议
    第七次会议
  • 原文地址:https://www.cnblogs.com/yangxuming/p/7417180.html
Copyright © 2020-2023  润新知