前言
本日跟大家聊聊日常开辟中,怎样淘汰bug?本文将从数据库、代码层面、缓存使用篇3个大方向,总结出一共50多个留意点,助大家成为开辟质量之星。
1. 数据库篇
慢查询
数据库篇的话,哪些地方容易导致bug出现呢?我总结了7个方面:慢查询、数据库字段留意点、事务失效的场景、死锁、主从延迟、新老数据兼容、一些SQL经典留意点。
1.1 慢查询
慢查询.gif
1.1.1 是否命中索引
提起慢查询,我们马上就会想到加索引。如果一条SQL没加索引,或者没有命中索引的话,就会产生慢查询。
索引哪些情况会失效?
- 查询条件包含or,可能导致索引失效
- 怎样字段类型是字符串,where时一定用引号括起来,否则索引失效
- like通配符可能导致索引失效。
- 联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
- 在索引列上使用mysql的内置函数,索引失效。
- 对索引列运算(如,+、-、*、/),索引失效。
- 索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。
- 索引字段上使用is null, is not null,可能导致索引失效。
- 左毗连查询或者右毗连查询查询关联的字段编码格式不一样,可能导致索引失效。
- mysql估计使用全表扫描要比使用索引快,则不使用索引。
1.1.2 数据量大,考虑分库分表
单表数据量太大,就会影响SQL执行性能。我们知道索引数据结构一般是B+树,一棵高度为3的B+树,大概可以存储两千万的数据。凌驾这个数的话,B+树要变高,查询性能会下降。
因此,数据量大的时候,建议分库分表。分库分表的中心件有mycat、sharding-jdbc
1.1.3 不合理的SQL
日常开辟中,笔者见过很多不合理的SQL:好比一个SQL居然用了6个表毗连,连表太多会影响查询性能;再好比一个表,居然加了10个索引等等。索引是会降低了插入和更新SQL性能,以是索引一般不建议太多,一般不能凌驾五个。
1.2 数据库字段留意点
数据库字段这块内容,很容易出bug。好比,你测试环境修改了表结构,加了某个字段,忘记把脚本带到生产环境,那发版肯定有问题了。
1.2.1 字段是否会超长
假设你的数据库字段是:
`name` varchar(255) DEFAULT NOT NULL如果哀求参数来了变量name,字段长度是300,那插入表的时候就报错了。以是需要校验参数,防止字段超长。
1.2.2 字段为空,是否会导致空指针等
我们计划数据库表字段的时候,只管把字段设置为not null。
- 如果是整形,我们一般使用0或者-1作为默认值。
- 如果字符串,默认空字符串
如果数据库字段设置为NULL值,容易导致程序空指针;如果数据库字段设置为NULL值,需要留意count(详细列) 的使用,会有坑。
1.2.3 字段缺失
我们的日常开辟任务,如果在测试环境,对表进行修改,好比添加了一个新字段,必须要把SQL脚本带到生产环境,否则字段缺失,发版就有问题啦。
1.2.4 字段类型是否支持心情
如果一个表字段需要支持心情存储,使用utf8mb4。
1.2.5 谨慎使用text、blob字段
如果你要用一个字段存储文件,考虑存储文件的路径,而不是保存整个文件下去。使用text时,涉及查询条件时,留意创建前缀索引。
1.3 事务失效的场景
1.3.1 @Transactional 在非public修饰的方法上失效
@Transactional注解,加在非public修饰的方法上,事务是不会生效的。spring事务是借鉴了AOP的思想,也是通过动态署理来实现的。spring事务自己在调用动态署理之前,已经对非public方法过滤了,以是非public方法,事务不生效。
1.3.2 当地方法直接调用
以下这个场景,@Transactional事务也是无效的
public class TransactionTest{ public void A(){ //插入一条数据 //调用方法B (当地的类调用,事务失效了) B(); } @Transactional public void B(){ //插入数据 }}1.3.3 异常被try...catch吃了,导致事务失效。
@Transactionalpublic void method(){ try{ //插入一条数据 insertA(); //更改一条数据 updateB(); }catch(Exception e){ logger.error("异常被捕获了,那你的事务就失效咯",e); }}1.3.4 rollbackFor属性设置错误
Spring默认抛出了未检查unchecked异常(继续自RuntimeException 的异常)或者Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,就需要指定rollbackFor属性。
1.3.5 底层数据库引擎不支持事务
MyISAM存储引擎不支持事务,InnoDb就支持事务
1.3.6 spring事务和业务逻辑代码必须在一个线程中
业务代码要和spring事务的源码在同一个线程中,才会受spring事务的控制。好比下面代码,方法mothed的子线程,内部执行的事务操作,将不受mothed方法上spring事务的控制,这一点大家要留意。这是由于spring事务实现中使用了ThreadLocal,实现同一个线程中数据共享。
@Transactionalpublic void mothed() { new Thread() { 事务操作 }.start();}1.4 死锁
死锁是指两个或多个事务在同一资源上相互占用,并哀求锁定对方的资源,从而导致恶性循环的征象。
MySQL内部有一套死锁检测机制,一旦发生死锁会立即回滚一个事务,让另一个事务执行下去。但死锁有资源的利用率降低、进程得不到正确结果等危害。
1.4.1 9种情况的SQL加锁分析
要制止死锁,需要学会分析:一条SQL的加锁是怎样进行的?一条SQL加锁,可以分9种情况进行探讨:
- 组合一:id列是主键,RC隔离级别
- 组合二:id列是二级唯一索引,RC隔离级别
- 组合三:id列是二级非唯一索引,RC隔离级别
- 组合四:id列上没有索引,RC隔离级别
- 组合五:id列是主键,RR隔离级别
- 组合六:id列是二级唯一索引,RR隔离级别
- 组合七:id列是二级非唯一索引,RR隔离级别
- 组合八:id列上没有索引,RR隔离级别
- 组合九:Serializable隔离级别
1.4.2 怎样分析解决死锁?
分析解决死锁的步骤如下:
- 模仿死锁场景
- show engine innodb status;查看死锁日志
- 找出死锁SQL
- SQL加锁分析,这个可以去官网看哈
- 分析死锁日志(持有什么锁,等待什么锁)
- 熟悉锁模式兼容矩阵,InnoDB存储引擎中锁的兼容性矩阵。
有兴趣的小伙伴,可以看下我之前写的这篇文章:手把手教你分析Mysql死锁问题
1.5 主从延迟问题考虑
先插入,接着就去查询,这类代码逻辑比较常见,这可能会有问题的。一般数据库都是有主库,从库的。写入的话是写主库,读一般是读从库。如果发生主从延迟,,很可能出现你插入成功了,但是查询不到的情况。
1.5.1 要求强一致性,考虑读主库
如果是重要业务,要求强一致性,考虑直接读主库
1.5.2 不要求强一致性,读从库
如果是一般业务,可以担当短暂的数据不一致的话,优先考虑读从库。由于从库可以分担主库的读写压力,进步系统吞吐。
1.6 新老数据兼容
1.6.1 新加的字段,考虑存量数据的默认值
我们日常开辟中,随着业务需求变更,常常需要给某个数据库表添加个字段。好比在某个APP配置表,需要添加个场景号字段,如scene_type,它的枚举值是 01、02、03,那我们就要跟业务对齐,新添加的字段,老数据是什么默认值,是为空照旧默认01,如果是为NULL的话,程序代码就要做好空指针处置惩罚。
1.6.2 如果新业务用老的字段,考虑老数据的值是否有坑
如果我们开辟中,需要沿用数据库表的老字段,而且有存量数据,那就需要考虑老存量数据库的值是否有坑。好比我们表有个user_role_code 的字段,老的数据中,它枚举值是 01:超级管理员 02:管理员 03:一般用户。假设业务需求是一般用户拆分为03查询用户和04操作用户,那我们在开辟中,就要考虑老数据值的问题啦。
1.7 一些SQL的经典留意点
1.7.1 limit大分页问题
limit大分页是一个非常经典的SQL问题,我们一般有这3种对应的解决方案
方案一: 如果id是一连的,可以如许,返回上次查询的最大记载(偏移量),再往下limit
select id,name from employee where id>1000000 limit 10.方案二: 在业务答应的情况下限制页数:
建议跟业务讨论,有没有必要查这么后的分页啦。由于绝大多数用户都不会往后翻太多页。谷歌搜索页也是限制了页数,因此不存在limit大分页问题。
方案三: 利用延迟关联或者子查询优化超多分页场景。(先快速定位需要获取的id段,然后再关联)
SELECT a.* FROM employee a, (select id from employee where 条件 LIMIT 1000000,10 ) b where a.id=b.id1.7.2 修改、查询数据量多时,考虑分批进行。
我们更新或者查询数据库数据时,只管制止循环去操作数据库,可以考虑分批进行。好比你要插入10万数据的话,可以一次插入500条,执行200次。
正例:
remoteBatchQuery(param);反例:
for(int i=0;i |