之前写过一篇《日常编程中的小技巧以及注意点(三)》,感兴趣可以查看。
冗余字段应遵循:
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
说明:如果预计三年后的数据量无法达到这个级别,请不要在创建表时就分库分表。
正例:见下表,其中无符号值可以避免误存负数,且扩大了数据的表示范围。
对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 150岁之内 | unsigned tinyint | 1 | 无符号值;0至255 |
龟 | 数百岁 | unsigned smallint | 2 | 无符号值;0至65535 |
恐龙化石 | 数千万年 | unsigned int | 4 | 无符号值;0至约42.9亿 |
太阳 | 约50亿年 | unsigned bigint | 8 | 无符号值;0至约 |
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但会明显提高查找速度;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
说明:即使双表join也要注意表索引、SQL性能。
正例:如果where a=? and b=?,a列几乎接近于唯一值,那么只需要单建idx_a索引即可。
说明:如存在非等号和等号混合判断条件,在建索引时,请把等号条件的列前置。如where a>? and b=?,那么即使a的区分度更高,也必须把b放在索引的最前列。
正例:可以使用如下方式来避免sum的NPE问题:SELECT IF(ISNULL(SUM(g)), 0, SUM(g)) FROM table;
说明:NULL与任何值的直接比较都为NULL。
- NULL<>NULL的返回结果是NULL,而不是false。
- NULL=NULL的返回结果是NULL,而不是true。
- NULL<>1的返回结果是NULL,而不是true。
说明:以学生和成绩的关系为例,学生表中的student_id是主键,那么成绩表中的student_id则为外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
说明:1.增加查询分析器解析成本。
2.增减字段容易与resultMap配置不一致。
SELECT *增加很多不必要的消耗(CPU、IO、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所有要求直接在select后面接上字段名。
传入为POJO类,如果不管是否为自己的目标更新字段进行update table set c1=value1, c2=value2, c3=value3;是不对的。执行SQL时,不要更新无改动的字段,一是容易出错,二是效率低,三是会增加binlog存储。
说明:单一原则是最易理解却又最难实现的一条规则,随着系统的演进,工程师很多时候会忘记类设计的初衷。
说明:随着代码的重复次数不断增加,维护成本呈指数级上升。
大部分时,方法局部引用变量所引用的对象会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部引用变量显式设为null。
例如:
public void test() {
Object obj = new Object();
//do something...
obj = null;
}
上面这个就没必要了,随着方法test()的执行完成,程序中obj引用变量的作用域就结束了。但如果是下面这样的情况:
public void test() {
Object obj = new Object();
//do something...
obj = null;
//执行耗时,耗内存的操作;或调用耗时,耗内存的方法
//do something...
}
这时候就有必要将obj赋值为null,可以尽早的释放对Object对象的引用。
判断一个数是否是奇数,你可能会这样做:
if (n % 2 == 1) {
doSomething();
}
不过我们用与或运算的话会快很多。例如判断是否是奇数,我们就可以把n和1相与了,如果结果为1,则是奇数,否则就不会。即:
if (n & 1 == 1) {
doSomething();
}
同理,判断偶数就可以用“n & 1 == 0”的方式来进行判断,但是还有一种判断偶数的方式也是比较好的:
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:
select id from t where num in(1,2,3)
对于连续的数值,能用between就不要用in了;再或者使用连接来替换。
加上LIMIT 1,只要找到了对应的一条记录,就不会继续向下扫描了,效率会大大提高。LIMIT 1适用于查询结果为1条(也可能为0)会导致全表扫描的SQL语句。
如果要查询的字段是索引的话,就不需要加上LIMIT 1,如果是根据主键查询一条记录也不需要LIMIT 1,主键也是索引。
例如:
SELECT * FROM t_user WHERE id=?;
就不需要写成:
SELECT * FROM t_user WHERE id=? LIMIT 1;
二者效率没有区别。
union和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。
select * from 表A where id in (select id from 表B)
上面SQL语句相当于:
select * from 表A where exists(select * from 表B where 表B.id=表A.id)
区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。
关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。如何高效地写出一个替代not in的SQL语句呢?
原SQL语句:
select colname ... from A表 where a.id not in (select b.id from B表)
高效的SQL语句:
select colname ... from A表 where not exists(select b.id from B表 where 表B.id=表A.id)
select id,name from product limit 866613,20
使用上述SQL语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用limit分页查询会越来越慢。
优化的方法如下:可以取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866613。SQL可以采用如下的写法:
select id,name from product where id>866613 limit 20
比如:
select user_id,user_project from user_base where age*2=36;
上述SQL中对字段进行了算术运算,这会造成引擎放弃使用索引,建议改成:
select user_id,user_project from user_base where age=36/2;
对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。
参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。
被驱动表的索引字段作为on的限制字段。