本文参考了些博客,这里列出并感谢作者的辛勤奉献:
数据库设计的三大范式 - 小浩叔叔 - 博客园
为什么mysql中很少见到使用视图功能? - 知乎
salt值(盐值) - huhuahope - 博客园
版权归原作者所有,如有侵权请立即与我联系,我将及时处理。
本文规范以 阿里巴巴Java开发手册(嵩山版)作为核心依据,并进一步结合国内主流规范及某大厂资深DBA建议制作而成。
阿里巴巴Java开发手册 下载地址:
链接:https://pan.baidu.com/s/143BOFvaqHsnGFgj_EhdI4Q
提取码:Zze0
感谢 阿里巴巴团队,不愿意透露姓名的某大厂资深DBA 等雷锋的奉献。
开发团队成员需严格按照本文规范进行设计、开发。
版本号 | 修改人 | 更新日期 | 备注 |
---|---|---|---|
1.6.0 | Zze0 | 2021-05-13 | 出版MySQL开发规范第一版,版本号与阿里巴巴看齐 |
1.7.0 | Zze0 | 2021-05-14 | · 更新阿里巴巴Java开发手册(嵩山版)
· 完善表可选字段 |
1.7.0 | Zze0 | 2021-05-14 | · 更新阿里巴巴Java开发手册(嵩山版)
· 完善表可选字段 |
1.7.0-fix20210603 | Zze0 | 2021-06-03 | · 表必备字段中“is_delete”字段名变更为“is_deleted”, 由于类属性命名中对布尔类型需要去除is前缀,所以要避免ORM框架在生成“as delete”别名语句时报关键字错误的问题。
· 对核心表、业务表命名要求分别加上“sys_”、“t_”前缀。 |
数据库设计三大范式
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。
范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
第一范式(确保每列保持原子性,数据库表每一列都是不可分割的原子数据项)
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
其合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式。
第二范式(实体的属性完全依赖于主关键字,不能存在仅依赖主关键字一部分的属性)
第二范式在第一范式的基础之上更进一层。
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。
比如,现在有一张订单信息表,因为订单中可能会有多种商品,所以要将订单id和商品id作为了数据库表的联合主键。
订单id | 商品id | 商品名称 | 数量 | 单位 | 价格 | 客户 | 所属单位 | 联系方式 |
---|---|---|---|---|---|---|---|---|
1 | 1 | 挖掘机 | 1 | 台 | 1200000 | 张三 | 工厂A | 0757-12345678 |
1 | 2 | 冲击钻 | 10 | 把 | 2000 | 张三 | 工厂A | 0757-12345678 |
2 | 3 | 叉车 | 3 | 辆 | 1000000 | 李四 | 工厂B | 0757-87654321 |
这样就产生一个问题:这个表中是以订单id和商品id作为联合主键。
这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品id相关。所以在这里违反了第二范式的设计原则。
而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示:
订单信息表
订单id | 客户 | 所属单位 | 联系方式 |
---|---|---|---|
1 | 张三 | 工厂A | 0757-12345678 |
2 | 李四 | 工厂B | 0757-87654321 |
订单项目表
订单id | 商品id | 数量 |
---|---|---|
1 | 1 | 1 |
1 | 2 | 10 |
2 | 3 | 3 |
商品信息表
商品id | 商品名称 | 单位 | 价格 |
---|---|---|---|
1 | 挖掘机 | 台 | 1200000 |
2 | 冲击钻 | 把 | 2000 |
3 | 叉车 | 辆 | 1000000 |
这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品id到商品信息表中查询即可。
第三范式(任何非主属性不依赖于其他非主属性)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
比如在设计一个订单数据表的时候,可以将客户id作为一个逻辑外键和订单表建立相应的关系。而不要在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。
数据库
数据库名与项目名需一致且全部字母小写,且还需按环境来定义数据库名。项目名单词间隔字符为:“-”,数据库名单词间隔字符为:“_”。
- 正例:
项目名:
app-cloud
数据库名:
DEV环境:app_cloud_dev
SIT环境:app_cloud_sit
UAT环境:app_cloud_uat
PROD环境:app_cloud
单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
还需保持不同库和表之间使用相同的字符编码,相同的引擎,相同的表结构、表名以及索引。
分库分表可以控制单表的数据量,从而加快热数据的查询,分库分表规则可以根据业务特定规则,时间或者业务主键的hash值进行分库分表。
同时,分库分表不是简单的把库和表分开就行,需要结合分表规则对业务代码进行修改,如若按时间进行分表业务功能可以适当调整成根据时间进行历史数据和当前热数据等多个功能,从而在保证能覆盖大部分业务场景的前提下,减少多个分表之间的关联查询,减低编码复杂性。
数据库字符集使用utf8mb4。
数据库排序规则使用utf8mb4_general_ci。
表
所有表必须设置主键,强制要求使用数字类型,且由一个字段构成,最多不能超过3个。
MySQL在处理数值型主键会比字符型主键有更快的索引速度,而且通常数值型主键占用更少的存储空间;
若表没有主键InnoDB会默认设置隐藏的主键列,默认主键设置规则,若表中有唯一键,则使用唯一键作为主键,若表中没有唯一键,则会分配一个6-byte的rowId隐藏列作为主键,这个序列是InnoDB所有没有主键的数据表共享的,并发性能比较差;
MySQL所有索引都会保存索引值对应主键的值,若主键字段太多会占用额外的存储空间和IO。若业务逻辑要求3个或以上的字段作为联合主键,则可以额外创建一个逻辑主键字段,然后为业务主键添加一个联合唯一索引。
任何字段如果为非负数,必须是 unsigned
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 tinyint(1) unsigned 【1 表示是,0 表示否】
注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀。
数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。
- 正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。
表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。
- 正例:aliyun_admin,rdc_config,level3_name
- 反例:AliyunAdmin,rdcConfig,level_3_name
表名不使用复数名词。
表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
小数类型为 decimal,禁止使用 float 和 double。
在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text, 独立出来一张表,用主键来对应,避免影响其它字段索引效率。
MySQL单行字段总长度最大限制为65535,这里不包括TEXT、BLOB。
将大字段、访问频率低的字段拆分到单独的表中存储。一个表的字段数控制在30个以下。
有利于有效利用缓存,防止读入无用的冷数据,较少磁盘IO,同时保证热数据常驻内存提高缓存命中率。
时间字段原则上使用datetime类型,方便后期扩展。【具体看业务场景需要可以使用date,比如:出生日期等等】
表必备字段
字段 | 类型 | 注释 | 备注 | 示例 |
---|---|---|---|---|
id | bigint(20) unsigned | 主键id | 分布式ID(雪花算法) 默认由框架自动填充 | 1392747525787267073 |
creator_id | varchar(32) | 创建者id | 默认由框架自动填充 | 用户:u_1392747087507677186 |
create_time | datetime | 创建时间 | DEFAULT CURRENT_TIMESTAMP | 2021-05-13 07:37:28 |
modifier_id | varchar(32) | 更新者id | 默认由框架自动填充 | 用户:u_1392747087507677186 |
modify_time | datetime | 更新时间 | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 2021-05-13 07:37:28 |
is_delete | tinyint(1) unsigned | 是否删除 | DEFAULT 0 | 是:1 否:0 |
单纯的多对多的关系表(如:id、订单id、商品id)一般情况下是不需要定义以下字段【具体要看业务场景需要】:
- creator_id
- create_time
- modifier_id
- modify_time
表可选字段(一些常见字段的统一命名参考)
字段 | 类型 | 注释 | 备注 | 示例 |
---|---|---|---|---|
version | int(11) unsigned | 版本号 | 场景: 乐观锁 数据版本化管理 | 1 2 3 |
remark | varchar(4000) | 备注 | - | - |
code | varchar(50) | 编码 | 场景(跨环境数据关联): 数据字典编码 消息模板编码… | - |
serial_number | varchar(50) | 流水号/编号 | 场景: 项目编号 公文编号… | - |
state | int(11) | 状态 | status是MySQL的关键字,所以要改用state。 | 0 1 2 |
parent_id | bigint(20) unsigned | 父id | 场景(树形数据表): 分类 部门… | 1392747525787267073 |
sort | int(11) unsigned | 排序号 | - | 0 1 2 |
可选字段的长度请根据具体业务场景需要定义。
系统核心表的命名 遵循“sys_功能名称_表的作用”。
- 正例:sys_org_person / sys_org_department
业务表的命名 遵循“t_业务名称_表的作用”。
- 正例:t_alipay_task / t_force_project
表字段命名全部字母小写,单词间隔字符为:“_”,建议不超过32个字符。
原则上在长度不超32个字符的情况下,不要使用笼统的简写。在超过一定长度下可以通过合规的简写来缩短长度。
表和字段必须有注释说明。
相同含义的字段在不同表中应使用相同的名称,数据类型及长度必须保持一致。
如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。
字段允许适当冗余,以提高查询性能,但必须考虑数据一致。
冗余字段应遵循:
- 不是频繁修改的字段。
- 不是唯一索引的字段。
- 不是 varchar 超长字段,更不能是 text 字段。
- 正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。
合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
如:(unsigned)无符号值可以避免误存负数,且扩大了表示范围。
对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 150岁之内 | tinyint unsigned | 1 | 无符号值:0 到 255 |
龟 | 数百岁 | smallint unsigned | 2 | 无符号值:0 到 65535 |
恐龙化石 | 数千万年 | int unsigned | 4 | 无符号值:0 到约 43 亿 |
太阳 | 约 50 亿年 | bigint unsigned | 8 | 无符号值:0 到约 10 的 19次方 |
字段类型使用符合业务需求的最小类型,优先使用数值类型。
数值类型通常占用更少的存储空间,而且数值类型索引速度会更快,因此符合业务逻辑需求的情况下,能用数值型类型尽量使用数值型类型。
索引
主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;
另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律*,必然有脏数据产生。
*墨菲定律: 如果事情有变坏的可能,不管这种可能性有多小,它总会发生。
多表关联查询时,保证被关联的字段需要有索引。
即使双表 join 也要注意表索引、SQL 性能。
在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。
索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
- 正例:where a=? and b=? order by c; 索引:a_b_c
- 反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
利用覆盖索引来进行查询操作,避免回表。
如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
- 正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。
利用延迟关联或者子查询优化超多分页场景。
MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
- 正例:先快速定位需要获取的 id 段,然后再关联:
SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
- consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
- ref 指的是使用普通的索引(normal index)。
- range 对索引进行范围检索。
- 反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。
建组合索引的时候,区分度最高的在最左边。
存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即建立组合索引 idx_d_c。
- 正例:如果 where a=? and b=?,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
防止因字段类型不同造成的隐式转换,导致索引失效。
MySQL中若进行比较的类型不相同会进行默认的数据类型转换,如数值类型跟字符类型相比较MySQL会先将数据都转换成双精度数据,然后进行比较。若字符字段和数值类型比较,MySQL会将字符数据转换成数值再比较,字符列转换成数值类型后导致字符列字段不能使用索引从而拖慢整个查询效率。
适当结合使用唯一索引,尽量不要只在应用层通过“先查后插”方式处理。
对于可能设置为索引的字段,尽量设置为非空(not null)
BTree索引不能索引空值(null)。字段值为空的话,在部分查询不能有效使用索引。null 值走不了索引,占用多余的存储空间和IO
索引字段不超过表总字段的20%,创建索引需要考虑索引分布情况,尽量选择离散性比较强的数据进行索引。
若没有索引 MySQL 必须从第 1 条记录开始然后读完整个表直到找出相关的行。表越大,花费的时间越多。如果表中查询的列有命中索引,MySQL能快速找到数据所在的位置,而不必要扫描所有数据。
创建索引会占用额外的磁盘空间和降低DML效率,进行DML需要重新维护索引。
例如有一个 CHAR(200) 列,如果在前 10 个或 20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。对前 10 个或 20 个字符进行索引能够节省大量索引空间。
较小的索引涉及的磁盘 I/O 较少,较短的值比较起来更快。更为重要的是,对于较短的键值,索引高速缓存中的块能容纳更多的索引值,从而增加了命中缓存的概率。
SQL
超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致。
即使双表 join 也要注意表索引、SQL 性能。
不要使用 count(列名) 或 count(常量) 来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和 非NULL 无关。
count(*)会统计值为 NULL 的行,而 count(列名) 不会统计此列为 NULL 值的行。
但是 conut(*) 资源消耗比较大,应该尽量减少count操作,若业务逻辑要求频繁查询count数据可以根据试试性要求选择冗余数据保存,定期跑基准,对于实时性要求高的业务可以考虑使用redis或memcache等缓存中间件进行缓存并实时更新,同时为了数据准确性定期跑基准。
count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum() 时需注意 NPE 问题。
- 正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;
代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。(一般由框架实现)
不得使用外键与级联,一切外键概念必须在应用层解决。
(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
虽然设置数据库外键可以在一定程度上减少业务开发的复杂度,但是外键的维护需要额外的开销,通过外键“可达”另一张表就意味着锁,高并发时容易导出死锁。
禁止使用存储过程、触发器、视图、自定义函数等。
此类数据库复杂功能将业务运算放在数据库端执行,使系统维护的复杂性增大,业务开发人员不易理解存储过程等MySQL复杂功能的使用,DBA也不易理解此类复杂功能中的业务逻辑。
使用数据库这些特性会导致业务与数据库耦合,违背高伸缩性,降低移植性,过度依赖于数据库本身实现,不易于后期迁移升级存储方案,且难以调试、扩展。
跟Oracle不同的是,MySQL的存储过程不是一次编译的,每个会话在执行是都会重新编译一次,这导致了MySQL中执行效率会比Oracle差。
MySQL的视图不是一种物化视图,它相当于一个虚拟表,本身并不存储数据,当sql在操作视图时所有数据都是从其他表中查出来的。
这带来的问题是使用视图并不能将常用数据分离出来,优化查询速度。且操作视图的很多命令都与普通表一样,这会导致在业务代码中无法通过sql区分表和视图,使代码变得复杂。
实现视图的方式有两种,分别为合并算法和临时表算法。
合并算法是指查询视图时将视图定义的sql合并到查询sql中,比如create view v1 as select * from user where sex=m;当我们要查询视图时,mysql会将select id,name from v1;合并成select id,name from user where sex=m……;
临时表算法是先将视图查出来的数据保存到一个临时表中,查询的时候查这个临时表。
不管是合并算法和临时表算法都会带来额外的开销,且如果使用临时表后会使MySQL的优化变得很困难,比如索引。
而且视图还引入了一些其他的问题,使得其背后的逻辑非常复杂。当然,视图在某些情况下可以帮助提升性能,但时而性能也会被拖累,视图的性能很难预测。且在MySQL的优化器中,视图的代码执行路径也完全不同,无法直观的预测其执行性能。
数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。
对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。
- 正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;
- 反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column ‘name’ in field list is ambiguous。
in 操作需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。
不要使用 TRUNCATE TABLE
TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不要在开发代码中使用此语句。
不在索引列进行数学运算或函数运算
对索引字段进行运算会使得MySQL无法使用索引,导致全表扫描。
相同字段OR 使用in代替。不同字段OR使用UNION ALL或者UNION代替,优先考虑使用UNION ALL。
MySQL中OR操作效率是很低的,因此尽量减少OR操作的使用。UNION ALL不需要对结果进行排序。
拆分复杂SQL为多个小SQL。避免在SQL中进行业务运算,数据库的职责是数据存储,业务的运算尽量在应用中执行。
简单的SQL更容易使用到MySQL的QUERY CACHE加快查询效率,MySQL QUERY CACHE机制会对短时间内相同的查询,会直接从查询缓存中得到查询结果,而不再需要解析和执行查询。当表更改(包括表结构和表数据)后,查询缓存值的相关条目被清空;
MySQL不支持SQL并行查询,MySQL一个SQL只能使用一个CPU进行计算,大事务会导致后面的SQL等待。甚至有可能引发雪崩效应堵死数据库。
避免 % 前缀模糊查询 和 负向查询,NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等。
负向查询或者前缀模糊查询会使数据库使用不了Btree索引,从而导致全表扫描。
页面搜索原则上不要左模糊或者全模糊,如果需要请走搜索引擎来解决。
索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
ORM 映射
在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
- 增加查询分析器解析成本。
- 增减字段容易与 resultMap 配置不一致。
- 无用字段增加网络消耗,尤其是 text 类型的字段。
mapper.xml 配置参数使用:#{},不要使用${},此种方式容易出现 SQL 注入。
不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
- 反例:某同学为避免写一个
<resultMap>
,直接使用 HashTable 来接收数据库返回结果,结果出现日常是把 bigint 转成 Long 值,而线上由于数据库版本不一样,解析成 BigInteger,导致线上问题。
@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
数据
密码存储应该使用不可逆加密算法将密码及盐值(salt)组合加密后存储。防止关键信息泄露。
salt值属于随机值。用户注册时,系统用来和用户密码进行组合而生成的随机数值,称作salt值,通称为加盐值。
为用户密码添加Salt值,使得加密的得到的密文更加冷僻,不宜查询。即使黑客有密文查询到的值,也是加了salt值的密码,而非用户设置的密码。salt值是随机生成的一组字符串,可以包括随机的大小写字母、数字、字符,位数可以根据要求而不一样。
单向散列加密: 明文 ----> 单向散列算法(+salt) ----> 密文
为了加强单向散列计算的安全性,会给散列算法加点盐(salt),salt相当于加密的密钥,增加破解的难度。常用的单向散列算法有MD5、SHA等。
单向散列算法还有一个特点就是输入的任何微小变化都会导致输出的完全不同,这个特性有时也会被用来生成信息摘要、计算具有高离散程度的随机数等用途。
禁止单条记录超过8K
目前我们的DB一个page大小都设置为16K,当一条记录超过page的一半(8K)时,记录中的blob/varchar会在行外存储。存取时会有额外IO消耗。插入操作会锁住整个聚簇索引(X Index Lock),直到插入完成才释放。
服务
不同业务模块间不允许通过数据库进行交互
若不同业务模块间通过数据库进行交互会将两个业务模块紧密的耦合在一起,在后续分库分表等操作会导致多个业务系统需要配合修改,维护麻烦,同时这样的交互方式使得系统模块间结构不清晰。