[Java分布式架构实战]Java+MySQL开发规范

Mysql开发规范

  • 严禁在sql script中使用SELECT * , 业务当中需要使用哪些字段, 就返回哪些字段.
  • 统计行数时,使用 select count(*) from xxx
  • 所有的SQL关键字以及系统函数都使用全大写的方式. 例如: SELECT, INSERT, UPDATE, CURRENT_TIMESTAMP(), ORDER BY, GROUP BY, INNER JOIN, LEFT JOIN.
  • 多个表Join时, 必须指定别名, 参考如下的例子: 注意:应尽量减少表链接,同时注意驱动表的选择,索引等问题。
SELECT tb1.field1
    ,tb2.field2
FROM domain_table1 tb1
INNER JOIN domain_table2 tb2
    ON tb1.id = tb2.id
WHERE tb1.field2 = 'xxx'
ORDER BY tb1.in_date DESC
复制代码
  • 数据库,表,字段的编码使用utf8mb4
  • 字段名命名可读性要强,常用字段比如手机号,邮箱要保持相同的命名。
  • InDate,EditDate建议取数据库时间NOW(3)
  • 通常表中要有以下通用字段, id, status, system_status,in_user_id,in_user_name,in_date, edit_user_id, edit_user_name, edit_date
use test;
DROP TABLE IF EXISTS `test`.`system_role`;
CREATE TABLE `test`.`system_role` (
  `id` BIGINT AUTO_INCREMENT NOT NULL COMMENT '系统编号',
  `role_name` VARCHAR(45) NOT NULL COMMENT '角色名称',
  `status` TINYINT NOT NULL COMMENT '状态:0--无效,1--有效',
  `system_status` TINYINT NOT NULL COMMENT '系统状态:0--已删除,1--正常',
  `in_user_id` BIGINT NOT NULL COMMENT '创建人系统编号',
  `in_user_name` VARCHAR(45) NOT NULL COMMENT '创建人姓名',
  `in_date` DATETIME NOT NULL COMMENT '创建时间',
  `edit_user_id` BIGINT NOT NULL COMMENT '最后编辑人系统编号',
  `edit_user_name` VARCHAR(45) NOT NULL COMMENT '最后编辑人姓名',
  `edit_date` DATETIME NOT NULL COMMENT '最后编辑时间',
  PRIMARY KEY PK_system_role(`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COMMENT '系统角色';
复制代码
  • 数据库字段尽量不为NULL
  • 数据库字段不使用默认值。
  • 主键,外键,常用于检索的日期字段加索引,只有少量状态值的字段不加索引

MySQL设计反例及相关经验总结

回顾过去一两年参与的项目中遇到的一些不好的设计:

  • 字段长度过长,比如ProjectName varchar(500), 感觉真的有点不太合理,后来我专门去生产环境数据库中查询了一下最大长度只有131,所以根据这个情况,最大冗余255个字符就相对合理了。
  • 字段名设计不合理:项目的地理位置经度,纬度被设计为LocationX, LocationY。呃,显然不太好,应该是经度longitude,纬度latitude。
  • 数据库通用字段CommonStatus的值被定义为-999--已删除(逻辑删除),0--无效,1--有效。现在回过头来看,应该拆分为两个字段"系统状态"(SystemStatus TINY INT, 0--已删除,1--正常),数据状态(DataStatus TINYINT, 0--禁用,1--启用)
  • InDate,EditDate建议取数据库时间NOW(3)。有一些场景特别关注时间的,比如物联网云对云模式采集数据时,供应商数据上报时间会携带在消息体中,平台在接收到数据时会将消息扔到MQ中,MQ的订阅者在接收到消息时,会持久化。在这个场景中应该根据业务要求分别对待这些时间点。比如在数据持久化时建议使用MySQL系统时间NOW(3)。
  • 在一些关系表设计中,建议使用具体唯一性的字段做为唯一索引(比如role_id, user_id)。比如system_role_user
use test;
DROP TABLE IF EXISTS `test`.`system_role_user`;
CREATE TABLE `test`.`system_role_user` (
  `id` BIGINT AUTO_INCREMENT NOT NULL COMMENT '系统编号',
  `role_id` BIGINT NOT NULL COMMENT '角色编号',
  `user_id` BIGINT NOT NULL COMMENT '用户编号',
  `system_status` TINYINT NOT NULL COMMENT '0--已删除,1--正常',
  `in_user_id` BIGINT NOT NULL COMMENT '创建人系统编号',
  `in_user_name` VARCHAR(45) NOT NULL COMMENT '创建人姓名',
  `in_date` DATETIME NOT NULL COMMENT '创建时间',
  `edit_user_id` BIGINT NOT NULL COMMENT '最后编辑人系统编号',
  `edit_user_name` VARCHAR(45) NOT NULL COMMENT '最后编辑人姓名',
  `edit_date` DATETIME NOT NULL COMMENT '最后编辑时间',
  PRIMARY KEY PK_system_role_user(`id`),
  UNIQUE INDEX `IUX_role_id_user_id` (`role_id` ASC, `user_id` ASC))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COMMENT '角色与用户的关系';
复制代码
  • SQL_CALC_FOUND_ROWS在数据量比较大的表上使用时,性能不稳定。建议还是使用count(*)语句来计算总行数。或者在一些可以不关注总行数的业务场景中,可以不计算,比如app上的滚动到底部加载下一页的场景。
  • 在IN语句中不要有过多的值,比如限定不能有超过20个。超过了阈值的业务场景应该考虑换一种写法,比如使用临时表+连接的方式。
  • 在join,where的条件中如果字段的类型(比如一个是字符串,一个是数字)或编码不一致(比如一个是utf8,一个是utf8mb4)时,可能会导致full table scan或full index scan。
  • 要特别注意where语句中出现or, union等复杂子查询的场景。这种情况一般可以使用临时表+连接来优化性能。
DROP TEMPORARY TABLE IF EXISTS tmp_pro;
CREATE TEMPORARY TABLE tmp_pro
(
  ProjectSysNo INT,
  KEY IX_ProjectSysNo(ProjectSysNo)
)ENGINE=MEMORY;

INSERT INTO tmp_pro
(
ProjectSysNO
)
...
union
...

# 在下面的查询中,使用tmp_pro来驱动连接
复制代码
  • 尽量减少表连接,如果需要使用,那么需要特别关注相关连接字段,筛选条件,排序条件是否有索引,索引是否合理(比如考虑覆盖索引,减少filesort等等),驱动表选择是否合理。当连接比较多时,会导致代码难以理解,遇到问题时很容易无从下手。
  • 在大表分页场景,应尽量减少大表连接,可以先从大表主键分页,取当前分页的数据,然后再回表取每一行的数据。比如
DROP TEMPORARY TABLE
IF EXISTS tmp_current_page;

CREATE TEMPORARY TABLE tmp_current_page
(
SysNo INT,
KEY IX_SysNo(SysNo)
)ENGINE=MEMORY;

INSERT INTO tmp_current_page
(
SysNo
)
SELECT
a.`SysNo`
FROM
`xxx_warning`.`projectintellidevicewarning` AS a
<!--<if test="filter.userSysNo != null and filter.userSysNo != ''">
    inner join `xxx_warning`.`projectintelliwarninguser` u
    on a.ProjectSysNo = u.ProjectSysNo and a.AppSysNo = u.AppSysNo AND u.UserSysNo = #{filter.userSysNo}
    and u.CommonStatus=1
</if>-->
<where>
    <if test="filter.projectSysNo != null and filter.projectSysNo > 0">
        AND a.ProjectSysNo=#{filter.projectSysNo}
    </if>
    <if test="filter.appSysNo != null and filter.appSysNo > 0">
        AND a.AppSysNo=#{filter.appSysNo}
    </if>
</where>
ORDER BY
a.SysNo DESC
LIMIT #{offset},#{pageSize};

### 这里不用IN,然后由小表tmp_current_page驱动效果可能会更好吧!
;SELECT
...
FROM
`xxx_warning`.`projectintellidevicewarning` AS a
WHERE a.SysNo IN(SELECT SysNo FROM tmp_current_page)
ORDER BY a.`SysNo` DESC;
复制代码

Java开发规范

  • 所有Java Bean(DTO)必须实现Serializable,并生成serialVersionUID
@Data
public class ProjectAuditQueryFilter extends BaseQueryFilter implements Serializable {
    private static final long serialVersionUID = 8460765917245508495L;
}
复制代码
  • 记录详细的日志,比如重要的流程节点记录详细的有意义的warning log。又可以分为运维相关和业务相关。比如在写业务待办通知时,如果用户没有注册通道或者不允许推送,那么在写业务待办时就不会写推送通知。此时应该记录warning log, 以便于我们排查问题,关注业务动作过程。
  • mybatis中不使用mybatis.type-aliases-package, 在mapper中使用全类名
  • mybatis中使用resultMap,参数中指定更多的参数类型等信息,使用mybatis-generator来生成相关代码。
  • gitlab merge request: 我们使用gitlab的merge request来搞,每个人基于develop拉取最新的分支,每次提交代码前将develop(现在是新项目,就直接是develop, 以后是各个feature分支)合并到自己的分支,自己先将冲突解决掉,保证编译通过,冒烟测试通过,然后提交merge request, 由相关的人进行代码审核,审核通过后,会merge到develop。
  • 业务方法命名要直观容易理解,同时方法参数只能出现主键类简单类型,比如soId, supplierSkuId, retrunId等,其它场景 必须使用DomainModel来定义,不能直接使用简单参数。可以针对每个场景定义不同的DomainModel, 比如同步摄像头可以定义为SyncCameraRequest,

一些性能,安全方面的总结

  • 尽量减少取数据的数量: 取大量数据时,会耗费更多DB,redis, es等等磁盘,内存,CPU,网络等资源。
  • 延迟加载数据:尽量只加载用户第一眼需要看的数据,其它数据由用户的操作行为驱动。
  • 关键的高并发业务需要有压测,用数据来衡量系统容量,进而才能做好容量评估。
  • 要有明确的监控,灾难恢复等运维兜底方案。
参考资料
关注公众号交流学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值