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