1: 单元测试
提高 Dao 层 方法级别覆盖,包括 insert ,update ,select 。
Service 层核心方法覆盖,如对外部环境(网络、服务、中间件等)有依赖,引入Mock 实现。
核心业务、核心应用、核心模块的增量代码确保单元测试通过。
2. 禁止3B:Big Transaction,Big SQL,Big Batch
2.1. Big Transaction
注意点:
1. 对数据库操作必须使用事务,不能使用自动提交,尽量使用声明式事务;
2. 让事务尽可能的小,在Service层组装数据,在manager层处理事务;
3. 不要在事务里调用服务(服务可能阻塞);
4. 不要在事务里调用Redis;
5. 在事务中批量更新要排序,确保多事务并发时,避免资源锁等待。
详解:
无论是Oracle、SqlServer还是Mysql,大事务是一定要避免的,大事务容易造成锁资源的长时间占用,从而降低并发性能,增大死锁概率。如下是几种大事务的典型场景:
1) @Transactional不要打在Class上,这样类中的所有方法均在事务边界内,容易造成大事务,@Transactional应该控制更精细一些,打到方法级;
2) 在一个事务中要更新多张表,在更新每一张表之前都要处理一堆业务逻辑(查询、运算、调用服务等等),正确的做法应该是将查询、运算和服务调用逻辑提到事务外,事务边界内尽可能只处理表更新操作;
2.2. Big SQL
SQL使用:
1. 尽量不用表关联,如果使用表关联,不要超过3个表join;
2. 热点数据尽量使用Redis;(比如基础资料)
3. 尽量不用子查询,不用Exist,不在条件列上使用函数;
2.3. Big Batch
大批量的查询输出很容易将内存打爆,报表或者打印要分批处理。
3. 禁止全表扫描SQL和select *,update所有列
3.1. 全表扫描
什么是全表扫描?
在数据库中,对无索引表查询或有索引但SQL不能有效利用进行查询的过程称为全表扫描。
全表扫描会搜寻数据库表中的每一条记录,直到所有符合给定条件的记录返回。
常见的全表扫描SQL:
1. is (not) null
Sql代码
1. select ID from t where NUM is null
解决方式:不允许表中含可空字段,对未赋值的字段设默认值,根据默认值查询。
2. like “%***%” 和like ‘%***’
Sql代码
1. select id from t where name like '%abc%'
2. select id from t where name like '%abc’
可以用“全文检索”取代“全模糊查询”或“左模糊查询”,右模糊查询可以利用上索引。
3. where 子句中使用参数
Sql代码
1. select id from t where num=@num <mailto:num=@num>
可以改为强制查询使用索引:
Sql代码
1. select id from t with(index(索引名)) where num=@num <mailto:num=@num>
4. 在where子句中的条件列上进行函数、算术运算或其他表达式运算
Sql代码
1. select id from t where num/2=100
应改为:
Sql代码
1. select id from t where num=100*2
Sql代码
1. select id from t where substring(name,1,3)=’abc’–name以abc开头的id
2. select id from t where datediff(day,createdate,’2005-11-30′)=0–‘2005-11-30’生成的id
应改为:
Sql代码
1. select id from t where name like ‘abc%’
2. select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
5. OR条件连接:myisam表能用到索引,innodb不行
Sql代码
1. select ID from t where NUM=10 or NUM=20
可以这样查询:
Sql代码
1. select ID from t where NUM=10
2. union all
3. select ID from t where NUM=20
3.2. select *
查询需要的字段;
当需要查询全部字段时,写出全部字段名;
3.3. update所有列
1. 不能写通用的update SQL,按业务更新;
2. 按物理主键或业务主键进行update操作;
4. 禁止输入参数不做校验及服务直接抛出异常
4.1. 参数校验
1. 客户端,服务端参数增加双重校验逻辑
2. 防止数据库注入,跨站点脚本防范(XSS)
4.2. 服务异常
异常处理是系统内的一种错误处理机制,一般不用于跨系统调用;
通过定义错误码方式是处理跨系统错误的合适的方式;
返回错误码,更便于调用方根据不同的返回值进行不同的处理,抛出异常的方式实际上是将底层的实现细节暴露给调用方
5. 禁止服务及UI不做防重入
5.1. 服务防重 -接口幂等性解决方案
MQ消息重复:
在服务端防止重复数据被多次被插入到数据库。
常用的办法:
1. MD5防重:对记录取MD5值,收到重复的数据丢弃;
2. 在数据库加唯一索引;
3. 根据业务做防重:状态顺序,业务ID;
5.2. UI防重入
在用户操作完成后,应当将界面变为不可操作状态(比如:按钮不可点击等,不再相应enter事件等等)。
6. 核心场景开发
1:涉及核心场景需求需要提供活动图,序列图。
修改原有代码逻辑需要提供伪代码图。(如事务开启,提交,回滚,更新逻辑涉及前置条件,sql更新条件 )
需要review通过后才允许上线。
7 :禁止Worker扫描业务表
建议使用分布式任务调度框架;
7.1. Worker扫描业务表带来的问题
1. 增加对业务表的访问压力;
2. 如果涉及更新,影响并发性能;
7.2. 正确的做法
1. 建立独立的任务表;
2. 数据量大:基于时间做分区索引;
3. 处理完成的任务可以删除或转历史,保证任务表数据量比较少
8: 核心业务在设计阶段考虑支持对账机制
对账包括实时对账,离线对账。
8.1 实时对账
技术方案类似于 “ 旁路监控” 需要双方系统研发支持。
举例 A系统 B系统 需要对账。A 系统核心流程处理完发出消息 MQ 。 对账系统 C 订阅 A 发出的消息,调用B 系统提供的实时接口,根据查询结果判断业务是否正常。
8.2 离线对账
这里主要指 D+1 在数仓上部署脚本核对,
9: 涉及账户,金额类更新场景
举例在涉及账户额度,更新,退款,优惠券,折扣卡 金额类场景,在设计阶段考虑增加版本号,状态等字段。
更新时 where 条件 中增加前置状态 或 版本号 条件,更新后dao层返回影响行数,在服务层根据返回行数做业务判断,复杂场景要引入事务保证数据一致性。
10 : KYB,KYC
kyb(know your business) 了解你的业务
kyc(know your customer) 了解你的客户
业务的核心流程包括那几个节点? 业务风险最高的场景是那块? 我们依赖那些核心系统? 依赖那些核心接口?提供的接口靠谱么?
写接口支持防重吗?服务端防重还是客户端防重? 是提交实时返回结果还是消息异步通知结果? 消息丢失如何监控数据一致性?
读接口是实时查询数据库还是查询缓存?数据是否有延迟?业务能接受延迟吗?
如何及时发现线上问题?
什么时候做降级?什么时候做限流?