阿里规约
阿里巴巴 的代码规范( 阿里规约 )主要包含 Java 、 C++ 、 Python 等编程语言的编码规范。最新版本可通过其 GitHub 仓库获取。
参考文章:
命名规范
包名
统一小写:例如商品库存包com.example.product.stock,而不是com.example.productStock
分隔符间单词必须单语义:例如商品库存包com.example.product.stock,而不是com.example.product_stock、com.example.product-stock
参考:
【强制】 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用
单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例: 应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构)
类名
普通类和方法
- 驼峰命名:例如UserService,而不是User_service。
 - 禁止拼音英文混用;
 - 禁止下划线、美元符号起始。
 - 可读性比长度重要:命名尽可能短,但望文生义更重要
- 可读性高(推荐):下面这些类既保证了望文生义,又保证了缩写
- 商品描述类:DescriptionOfProduct 缩写为 ProductDesc
 - 应用程序配置类:ApplicationConfiguration 缩写为 AppConfig
 - 客户信息类:CustomerInformation 缩写为 CustInfo。
 
 - 可读性低(不推荐):
- AbstractClass缩写命名成 AbsClass;
 - PurchaseProduct类缩写成PurProduct;
 
 
 - 可读性高(推荐):下面这些类既保证了望文生义,又保证了缩写
 
参考:
【强制】 代码中的命名均不能以 下划线或美元符号 开始,也不能以 下划线或美元符号 结束。
反例: name / name / $name / name / name$ / name
【强制】 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,纯拼音命名方式更要避免采用。
正例: renminbi / alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
反例: DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3
【强制】 方法名、参数名、成员变量、局部变量都统一使用
lowerCamelCase风格,必须遵从驼峰形式。正例: localValue / getHttpMessage() / inputUserId
【强制】 杜绝完全不规范的缩写,避免望文不知义。
反例: AbstractClass缩写命名成 AbsClass;condition缩写命名成 condi,此类随意缩写严重降低了代码的可阅读性。
【推荐】 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。
正例: 在 JDK 中,表达原子更新的类名为:AtomicReferenceFieldUpdater。
反例: int a 的随意命名方式。
【推荐】 在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。
正例: startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例: startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
测试类
命名:待测类名+Test 。
举例:用户测试类UserServiceTest,而不是TestUserService。
参考:
【强制】 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;
 测试类命名以它要测试的类的名称开始,以 Test 结尾
抽象类
命名: Abstract+自定义类名。
举例:抽象支付方式类AbstractPaymentMethod/BasePaymentMethod,而不是AbstractPayment。
异常类
命名:自定义异常名+Exception。
例如商品找不到类ProductNotFoundException,而不是ProductNotFound。
枚举类
- 类名:自定义类名+Enum。 例如ProcessStatusEnum
 - 成员名:跟常量一样
 - 纯大写+下划线:例如最大库存量MAX_STOCK_COUNT。
 - 可读性比长度重要:常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。例如最大库存量MAX_STOCK_COUNT而不是MAX_COUNT。
 
枚举类和常量类区别:
相同点:在Java中,枚举和常量都用于表示一组固定的值,但它们适用的场景有所不同。
不同点:
- 枚举(Enum)适用于表示一组有限的可能取值,这些取值在程序中某个上下文中具有特殊意义。例如,表示一周的星期几或某个颜色的枚举类型。枚举类型可通过限定的值范围提供更好的类型安全性,并且可使用switch语句进行更清晰的代码编写。
 - 常量(Constant)适用于表示在程序中经常使用的不可变的值。常量一般定义为 final 类型,并且通常用于表示某个固定的数值或者字符串常量。常量的值在程序中是不可修改的,可以通过常量名直接使用,提高代码的可读性和可维护性,但是可能会降低类型安全性和表达能力。
 总结来说,枚举适用于表示一组有限的、具有特殊意义的取值,常量适用于表示程序中经常使用的不可变的值。
【参考】 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明: 枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。
正例: 枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON
接口和实现类
- service和dao实现类以Impl结尾。注意不要用Imp结尾。例如UserServiceImpl而不是UserServiceImp
 - 能力型接口以-able结尾。如异常根类Throwable是可抛出的,实现类Exception和Error都可抛出。如Drawable接口和Circle实现类。
 
参考:接口和实现类的命名有两套规则
【强制】 对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用Impl 的后缀与接口区别。
正例: CacheServiceImpl 实现 CacheService 接口。
【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形容词)。
正例: AbstractTranslator 实现 Translatable 接口。
PO、DTO、VO
- DO / BO / DTO / VO / AO / PO / UID 要大写,而非驼峰:例如UserDO,而不是UserDo。例如serialVersionUID,而不是serialVersionUid。
 - 禁止POJO后缀:POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
 
扩展:PO、DTO、VO
- 数据对象:xxxDO,xxx 即为数据表名。
 - DTO数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
 - VO展示对象:xxxVO,xxx 一般为网页名称。
 - POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
 参考:
【强制】 类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等。
正例: JavaServerlessPlatform / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例: javaserverlessplatform / UserDo / XMLService / TCPUDPDeal / TAPromotion
【参考】 各层命名规约:
A) Service/DAO 层方法命名规约
- 获取单个对象的方法用 get 做前缀。
 - 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
 - 获取统计值的方法用 count 做前缀。
 - 插入的方法用 save/insert 做前缀。
 - 删除的方法用 remove/delete 做前缀。
 - 修改的方法用 update 做前缀。
 
用到设计模式的类
案例参考文章:设计模式
概述
- 抽象类名以Abstarct/Base为前缀
 - 用到设计模式时要在命名中进行体现。
 
参考:
【推荐】 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
说明: 将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。
正例: public class OrderFactory; public class LoginProxy; public class ResourceObserver;
案例一:工厂设计模式采购披萨
采购披萨工厂类:利用工厂方法模式创建披萨时,可以把抽象工厂类命名为AbstractOrderPizzaFactory类。
1  | // 抽象工厂类,订购披萨类:  | 
案例二: 观察者设计模式订阅天气
天气局接口和观察者类:WeatherSubject和天气观察者接口WeatherObserver
变量
普通变量
- 驼峰命名:例如商品价格命名pruductPrice,而不是pruduct_price。
 - 禁止拼音英文混用;
 - 禁止下划线、美元符号起始。
 - 可读性比长度重要:命名尽可能短,但“望文生义”更重要
- 可读性高(推荐):下面这些类
- 最大值:maximumValue 缩写为 maxValue
 - 项目数量:numberOfItems 缩写为 numItems
 - 初始化计数器:initializeCounter 缩写为 initCounter
 
 - 可读性低(不推荐):
- 数量:count缩写为cnt
 - 值:value缩写成val
 - 序号:index缩写成i
 
 
 - 可读性高(推荐):下面这些类
 - 类型名词放词尾:词尾是类型,让人能一眼看出这个变量是干什么的。例如:
- 开始时间:startTime而不是startedAt。
 - 工作队列:workQueue而不是QueueOfWork。
 - 名字列表:nameList而不是listName。因为它是个列表。
 - 最大线程数:MAX_THREAD_COUNT而不是COUNT_MAX_THREAD 。它是数量而不是线程。
 
 
数组
中括号要直接跟在类型后面。
1  | // 错误示例:中括号放在变量名后面,注释放在右边  | 
参考:
【强制】 类型与中括号紧挨相连来表示数组。
正例: 定义整形数组 int[] arrayDemo;
反例: 在 main 参数中,使用 String args[]来定义
布尔型变量
实体类(PO类)里布尔型变量不能加is前缀,否则部分框架解析会引起序列化错误。同时MySQL表达“是否”的字段可以用is_xxx,只需<resultMap>设置从 is_xxx 到 xxx 的映射关系即可。示例: 
1  | public class User {  | 
为什么强制 boolean 类型变量不能使用 is 开头?
为了防止序列号失败。
- lombok序列号失败:javaBeans规范boolean变量的getter方法是isXXX(),其他变量的getter方法是getXXX()。lombok遵循javaBeans规范,如果一个变量是boolean isSuccess;在注解@Data或@Getter生成getter方法的时候,它会生成isSuccess()方法,而不是isIsSucess()方法。这也是lombok的一个大坑。
 - rpc框架序列号失败:在一些rpc框架里面,当反向解析读取到isSuccess()方法的时候,rpc框架会“以为”其对应的属性值是success,而实际上其对应的属性值是isSuccess,导致属性值获取不到,从而抛出异常。
 参考:
【强制】 POJO 类中布尔类型变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。
说明: 在 MySQL 规约中的建表约定第一条,表达是与否的值采用 is_xxx 的命名方式,所以,需要在<resultMap>设置从 is_xxx 到 xxx 的映射关系。
反例: 定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),RPC 框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。
父子类变量
父子类变量命名:避免同名变量。例如父类有个name变量,子类想声明一个昵称变量,应另外声明nickname,而非再次声明name。
参考:
【强制】 避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。
说明: 子类、父类成员变量名相同,即使是 public 类型的变量也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非 setter/getter 的参数名称也要避免与成员变量名称相同。
反例:
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 public class ConfusingName {
private int age;
// 非 setter/getter 的参数名称,不允许与本类成员变量同名
public void getData(String alibaba) {
if (condition) {
final int money = 531;
// ...
}
for (int i = 0; i < 10; i++) {
// 在同一方法体中,不允许与其它代码块中的 money 命名相同
final int money = 615;
// ...
}
}
}
class Son extends ConfusingName {
// 不允许与父类的成员变量名称相同
private int sonAge;
}
局部变量
避免同一个方法的多个代码块中声明同名局部变量。例如下面方法的money变量不符合规范:
1  | public void getData(String alibaba) {  | 
常量
普通常量
纯大写+下划线:例如最大库存量MAX_STOCK_COUNT。
可读性比长度重要:常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。例如最大库存量MAX_STOCK_COUNT而不是MAX_COUNT。
常量类细化:不要一个总的常量类维护所有常量,要按功能创建粒度小的常量类。例如电商平台商品模块不应该一个ProductStatusConstants维护所有商品常量,而应该细化成商品类型常量类ProductTypeConstants、商品价格范围常量类PriceRangeConstants、商品属性相关常量类ProductAttributeConstants等。
枚举类:变量值只在固定几个值内变化时用枚举类。例如季节Season变量只会有春夏秋冬四种类型,可以定义成季节枚举类。
1
2
3
4
5
6
7
8
9
10
11
12public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq) {
this.seq = seq;
}
public int getSeq() {
return seq;
}
}
参考:
【强制】 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
正例: MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例: MAX_COUNT / EXPIRED_TIME
【推荐】 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。
说明: 大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。
正例: 缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
【推荐】 如果变量值仅在一个固定范围内变化用 enum 类型来定义。
说明: 如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。
Long常量
大写L:以便于区分“L”和数字“1”。例如Long a=1L,而不是Long a=1l。
1  | // 错误示例:  | 
参考:
【强制】 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。
说明: Long a = 2l; 写的是数字的 21,还是 Long 型的 2。
代码格式
编码和换行符
规范:代码的编码统一使用UTF-8,换行符使用 Unix 格式,不要使用 Windows 格式。
换行符:
Unix格式:在Unix、Linux以及类Unix操作系统中,使用换行符\n来表示新行,这是常用的行结束符。
Windows格式:在Windows操作系统中,通常使用回车符和换行符\r\n(CR-LF,Carriage Return-Line Feed)来表示新行。
IDEA编码设置UTF-8:File → Setting → Editor → File Coding → Global Encoding;Project Encoding;Default encoding for properties files → 都选择 UTF-8
空格规范
保留字
规范:if/for/while/switch/do 等保留字与括号之间都必须加空格。
1  | if (condition) {  | 
Java常见保留字:
- 条件语句关键字:如if、else、switch、case、default。
 - 循环控制关键字:如for、while、do、break、continue。
 - 访问修饰符关键字:如public、private、protected。
 - 数据类型关键字:如int、double、char、boolean。
 - 类和对象关键字:如class、new、extends、implements。
 - 异常处理关键字:如try、catch、throw、throws、finally。
 
二目、三目运算符
规范:任何二目、三目运算符的左右两边需要加一个空格。
1  | int result = a + b;  | 
java中常见的二目、三目运算符:
- 二目运算符:+、-、*、/、%、=、==、>=、&&
 - 三目运算符:?:
 
缩进
规范:采用 4 个空格缩进。可以使用tab快捷键直接四个空格,禁止直接使用“tab”字符 。
IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。
IDEA设置 tab:File → Setting → Editor → Code Style → Tabs and Indents → 取消勾选Use tab character → Indent 设置4
注释
规范:注释双斜线后紧跟一个空格。
1  | // 正例: 这是示例注释,请注意在双斜线之后有一个空格  | 
强制转换
规范:强制转换时,右括号后无空格。
1  | // 正例:  | 
方法参数
规范:方法参数逗号后加一个空格。
1  | // 正例:下例中实参的 args1,后边必须要有一个空格。  | 
行数、字符数
单行字符数
规范:单行字符数不超过120个,超过时需要换行。
换行规则:
第2/3/4/..行相对第一行缩进 4 个空格。
运算符随下文一起换行。例如a + b,应该换行成a\n + b,而不是a + \nb
点符号与下文一起换行。例如dog.eat(),应该换行成dog\n.eat(),而不是dog.\neat()
方法的多个参数换行时,逗号不与下文一起换行。例如sum (1,2,3),应该换行成sum (1,2,\n3),而不是sum (1,2\n,3)
在括号前不换行。例如fun (a),应该换行成\nfun (a),而不是fun \n(a)
1  | // 正例:  | 
方法行数
规范:单个方法的总行数不超过 80 行。
除注释之外的方法签名、左右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80 行。
括号
大括号换行规则
规范:大括号内为空时无需换行。
1  | // 正例  | 
规范:大括号内非空时:
- 左大括号:前不换行,后换行
 - 右大括号:前换行,右换行(有else时不换行)
 
1  | // 正例  | 
小括号规则
规范:小括号内侧不隔空格,外侧隔单空格。
1  | // 正例:小括号内侧不隔空格,外侧隔单空格。  | 
MySQL 规约
建表规约
库
命名:库名与应用名称尽量一致;
表
- 大小写:MySQL表名不能有大写字母。因为MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。
 - 复数:不可使用复数。
 - 不可使用保留字:例如不能命名为add,from,set等。
 - 业务名称_表的作用:建议命名“业务名称_表的作用”,例如:
- 用户信息表:user_info
 - 产品信息表:product_info
 - 客户订单关联表:customer_order_relation
 
 
字段
基础命名规范
- 命名要慎重:字段名的修改代价很大,所以必须要慎重;
 - 大小写:MySQL字段名不能有大写字母。因为MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写,大小写混用会出问题。
 - 保留字:不可使用保留字。例如不能命名为add,from,set等。
 
基本规范
- 注释:字段含义改变时,及时更新注释;
 - 合理冗余:多读少写、长度短、非唯一索引的字段可以冗余,以降低连表查询的次数。
 - 关联字段类型:要关联查询的两个字段,数据类型必须一致。如果不一致会导致索引失效,索引和索引失效场景具体可以参考顶部导航文章中的“MySQL高级篇”;
 - 分库分表依据:单表数据量五百万条数据,或者容量2GB。
 - 三大必备字段:主键、创建时间、修改时间。即id, create_time(或者命名为gmt_create), update_time(或者命名为gmt_modified)
 
布尔型字段
结构:is_xxx
数据类型:unsigned tinyint
值:1 表示是,0 表示否
对应实体类变量:虽然数据库必须命名成is_xxx,但是该表对应的实体类成员变量不能命名为isXxx,否则会导致序列号失败。故需要在
resultMap中进行字段与属性之间的映射。
《阿里规约》原文:
【强制】 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。
说明: 任何字段如果为非负数,必须是 unsigned 。
注意: POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在< resultMap >设置从 is_xxx到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。
正例: 表达逻辑删除的字段名 is_deleted ,1 表示删除,0 表示未删除。
小数
- 类型:decimal。主要是为了防止丢失精度。
 
【强制】 小数类型为 decimal ,禁止使用 float 和 double 。
说明: 在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。
字符串
基本规范
- 长度几乎固定字段:使用char类型。例如电话号、身份证字段类型char(11)即可,效率要比varchar(11)更高。因为实际存储时,varchar会根据实际输入的内容占用的长度进行存储,因此占用的存储空间是实际内容长度+可变长字段长度(当varchar使用长度≤255时使用一个字节记录,长度超出255时使用二个字节记录)。
 - 超长字段:长度超过 5000的超长字段,一律使用text类型,并将该字段独立出一个表。因为text、blog类型会导致索引失效;不使用varchar是因为varchar(5000)太长,建索引后非聚簇索引树过于占用磁盘空间。
 
【强制】 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
【强制】 varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text ,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
varchar和char类型的区别、适用场景
长度:
- char:固定长度的字符串
 - varchar:可变长度的字符串。
 
存储方式:
- char:长度固定不可变,未存满的值会用空格填充到固定的长度。因此char类型字符串末尾无法存储空格,当然也不需要额外字节记录字符串长度。
 - varchar:varchar会使用1或2个额外字节记录字符串的长度。当列最大长度是255及以下时,varchar会使用一个字节记录可变长长度,最大长度255以上会使用两个字节记录可变长长度。因为varchar有记录长度,所以字符串末尾可以存储空格。
 
存储容量:
- char:最多255个字符
 - varchar:理论上最多65535字节,最多65532个字符(当用utf-8编码存纯英文、且该表只有这一个字段时,字符串中的字符只占1个字节,能达到65532个字符)。但实际从性能考虑,超过5000长度时就不允许再用varchar,而是使用text类型。
 
各编码的占用长度
- GBK编码:一个英文字符占一个字节,中文2字节,单字符最大可占用2个字节。
 - UTF-8编码:一个英文字符占一个字节,中文3字节,单字符最大可占用3个字节。
 - utf8mb4编码:一个英文字符占一个字节,中文3字节,单字符最大占4个字节(如emoji表情4字节)。
 varchar(20) 是指字符串最大字节数是20,还是最大字符数是20?
答案:取决于MySQL版本;4.0版本及以下,MySQL中varchar长度是按字节展示,如varchar(20),指的是20字节;5.0版本及以上,MySQL中varchar长度是按字符展示。如varchar(20),指的是20字符。
为什么varchar理论上最多字符数是65532?
答案:因为MySQL行默认最大65535字节,varchar还需要1或2个字节维护可变长度,1个字节标识该列是否为NULL。
性能和空间:
- char:性能更好,每次更新时不用维护长度;但存在空间浪费的可能;
 - varchar:性能相对差一点,因为每次更新时要维护长度。如果更新后字符串变长后,原来的数据页正好存满,则需要耗费时间处理新字符串的存储;处理方式取决于存储引擎,例如MylSAM将行拆成多个片段存储,innoDB会分裂页。
 
适用场景:
- char:存储长度几乎固定的字符串适用char类型。例如电话号、身份证字段类型char(11)即可,效率要比varchar(11)更高。因为实际存储时,varchar会根据实际输入的内容占用的长度进行存储,因此占用的存储空间是实际内容长度+可变长字段长度(当varchar使用长度≤255时使用一个字节记录,长度超出255时使用二个字节记录)。
 - varchar:长度几乎不固定、不超过2000字符的字符串。
 
varchar和text类型的区别、适用场景
存储方式:
- VARCHAR:可变长度的字符数据类型,它需要指定最大长度。实际存储时,会根据实际输入的内容占用的长度进行存储,因此占用的存储空间是实际内容长度加上一些额外的长度信息。
 - TEXT:TEXT也用于存储可变长度的字符数据,但它可以存储非常大的文本内容,通常可以存储几GB的数据。
 
索引和查询:
- VARCHAR:由于VARCHAR有固定的最大长度,可以建立更有效率的索引,同时在查询时会更快一些。
 - TEXT:对于较大的文本数据,使用TEXT类型可能会导致一些查询性能上的损失,因为文本数据的处理通常会比较耗费资源。
 
使用场景:
- VARCHAR:适用于长度可预期且不会太长的文本内容,比如姓名、地址等信息。
 - TEXT:适用于长度不确定或者非常长的文本内容,比如文章内容、评论等。
 
优缺点:
- VARCHAR:占用存储空间相对较小,适合存储较短字符串,支持索引,查询速度较快。但最大长度的限制可能会带来一些不便。
 - TEXT:可以存储非常大的文本内容,并且没有固定长度的限制,适合存储较长的文本数据。但是在查询和索引上可能会稍慢,而且在某些情况下,可能会消耗更多的存储空间。
 
外键/级联
禁用外键和级联。因为外键影响数据库的插入速度,每次插入时都要检查、更新外键;级联更新是强阻塞,也会影响性能。外键与级联更新适用于单机低并发的场景,不适合分布式、高并发集群的场景
级联删除:创建外键时声明级联,则引用表删除数据时,被引用表也会级联删除这条数据。示例
1
FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCAD
级联更新:创建外键时声明级联,则引用表更新数据时,被引用表也会级联更新这条数据。示例
1
FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON UPDATE CASCAD
索引
命名规范
【强制】 主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。
说明: pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
创建规范
唯一索引:唯一特性字段必须创建唯一索引。一般不需要另外创建,因为创建唯一约束的时候会自动创建唯一索引。
关联查询:被驱动表优先建索引,超过三个表禁止 join。
字符串:
- 模糊查询:禁止左模糊、全模糊索引。因为模糊查询会使索引失效,解决方案是使用ES等搜索引擎实现页面的搜索。
 - 索引长度:必须使用前缀索引。即字符串创建索引时必须指定索引长度,具体索引长度应该在区分度较高的前提下,索引长度越短越好。区分度=count(distinct left(列名, 索引长度))/count(*),即统计重复次数。
 
排序:保持联合索引的有序性。例如搜索条件where a=? and b=? order by c;,则创建联合索引:a_b_c
联合索引:区分度高的字段放左边。
覆盖索引:使用覆盖索引防止回表;例如查询where a=? and b=? and c=?,则创建联合索引a_b_c,而不是a_b,因为走a_b_c的时候,直接在非聚簇索引树就能获取到所有要查询的字段,不需要回表查聚簇索引树。
子查询优化深分页:正常情况下,深分页查询性能是很差的,例如我需要1w页第一条数据,那么就需要查出前1w条数据,性能很慢。用子查询可以优化深分页。
深分页查询优化:需求是返回第1000000~1000010 的记录。如果直接limit 100000,10,将会先排序前十万条数据并回表,查询速度会非常慢,甚至会超时。
主键有序的表根据主键排序,先过滤再排序:直接查上页最后记录之后的几个数据。
1
2
3
4# 自增。适用于app端和web端。由于不建议用自增策略(不安全、8.0才修复的ID回溯问题),所以此方法适用性不广。
SELECT * FROM student WHERE id > 99999 LIMIT 10;
# 雪花。x是上页最后一条记录的id。只适用于app端上下滑动分页时候必能拿到上页记录id。
SELECT * FROM student WHERE id > #{x.id} LIMIT 10;主键不有序的表根据主键排序,先给主键分页,然后内连接原表:当前表内连接排序截取后的主键表,连接字段是主键。因为查主键是在聚簇索引树查,不用回表,排序和分页很快
1
SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 1000000,10) a WHERE t.id = a.id;
主键有序的表根据非主键排序:得到上一页最后一条记录x(app端通过下拉翻页是肯定能获得上页最后记录的),那么目标页码的所有记录id都比x.id小(因为逆序,且排序依据其实是age,id,主键自增),目标页码的所有记录age都比x.age小或等于。
1
2
3# 自增
EXPLAIN SELECT * FROM student WHERE id<#{x.id} AND age>=#{x.age} ORDER BY age DESC LIMIT 10;
# 雪花一个思路,只是x.id通过子查询获取
命中索引的要求:SQL 性能优化目标至少达 range 级别(范围索引),要求是 ref 级别(非唯一索引),最好const(唯一索引)。
SQL语句
基本规范
- 更新前要先查询:删除、更新前要先查询,避免误操作。
 - 禁用存储过程;
 - 禁用外键、级联。
 
查询字段
禁用select *。原因:
性能:多查询一些不需要的字段,性能差;
失去覆盖索引的可能性:在命中联合索引时,查询的字段正好在非聚簇索引树中,就不需要回表了,而如果select *,则一定需要回表,影响性能。
对比select 全部字段:即使需求是查询全部字段,也尽量用select 全部字段,而不用select *。原因:
性能:select * 在系统解析的时候会多一步从系统表获取具体字段的步骤,因此会比select 全部字段多花时间,效率稍低。
结果顺序:select 全部字段,查询的结果字段顺序可控;
应用场景:某些特例也是可以用select *的,例如一些特定场景,在开发过程中(非生产环境),表结构、字段名频繁变化,可以暂时用select *
查询数量:正确区分count(*)、count(1)、count(字段)
- count(*):统计包括null的所有行数
 
count(1):统计包括null的第一列的行数。因为第一列在每一列都存在,所以等同于统计了所有行,并且不需要检查各行数据, 所以性能可能略高于count(*)
- count(字段):统计不包括null的字段列的行数。例如学生表有100行,name列全是null,select count(name) from student查出的结果是0.
 
求和:当某一列值全是null时,count(col)的值是0,sum(col)的值是null,所以求和时要防止空指针异常。
分页查询
先查数量再查询:分页查询前先查询count,如果count为0,则直接返回数据为null,不再分页查询,提高效率。
对象关系映射(ORM)
禁用select *。
布尔型字段:数据库表用is_xxx,实体类禁用isXxx
结构:is_xxx
数据类型:unsigned tinyint
值:1 表示是,0 表示否
对应实体类变量:虽然数据库必须命名成is_xxx,但是该表对应的实体类成员变量不能命名为isXxx,否则会导致序列号失败。所系需要在 resultMap 中进行字段与属性之间的映射。
参数:使用#{},#param#,而不是${}。防止SQL注入。
返回值:强制禁用Map,虽然少去了序列号的过程,性能会快一点,但是字段类型不可控。
更新接口:更新时不要更新全部字段,尽量不要写一个参数为实体类的更新接口。一方面可以防止出错、另一方面可以提高性能、减少binlog存储(binlog是二进制日志文件,记录改不记录读,用于数据复制和数据恢复;在主从同步时用到)。
不要滥用事务:事务要尽可能的控制粒度,使粒度尽可能的小,例如一些不必要的查询可以放在事务外部,以减少锁冲突、缩短连接时长,从而提高QPS(每秒发送的请求数)




