有些编程规范是大部分编程语言通用的,特把它独立出来。
示例中我采用 Java 语言。有时也混杂其他语言。
本编码规范完全适用于 Java 和 Javascript 的编程规范,对其他语言也有借鉴作用。
什么样的规范才算是好的编程规范?
“大家好,才是真的好”。不是你觉得好就是好,而是绝大部分人都觉得这个编程规范好,才是一份好的规范。当然,如果你的编程风格与主流的不符合,我的建议是:强迫自己改正。
本编程规范主要参考 Google Java 编程规范,并且绝大部分规范符合谷歌编程规范,除了以下几点与其不同:
此外,本规范还参考了百度等互联网大公司的编程规范,主要是对谷歌编程规范的补充(谷歌编程规范不够详细,所涉及的规范主要是编程风格的)
当然,也有一些个人的见解。
维护别人写的代码,应当遵守一致原则。即修改后的代码的编程风格应该与之前的代码一致。
不同的要求严格程度不同,参照 RFC2119 分成三个等级:
[强制]
表示必须(MUST)或者不允许(MUST NOT)这样做。
[建议]
表示一般情况下应该(SHOULD)或者不应该(SHOULD NOT)这样做,但是在某些特定情况下可以忽视这个要求。
[可选]
表示这个要求完全是可选的,你可以(MAY)这样做,也可以不这样做,视个人喜好而定。
虽然谷歌推荐 2 个空格的缩进,但 4 个空格缩进的代码可读性明显更强。现在很多代码编辑器支持输入时自动把 tab 字符转化为 4 个空格符。
每当开始一个新的块,缩进增加2个空格,当块结束时,缩进返回先前的缩进级别。缩进级别适用于代码和注释。
// good
void foo() {
int a = 0;
}
// bad
int a = 0;
}
void foo() {
if (condition) {
// statement
}
}
switch (foo) {
case 1:
// code
break;
case 1:
// code
break;
default:
break;
}
// good
int foo = 0; // 这是一个注释示例
/* 这是一个注释示例 */
/** 这是一个注释示例 */
// bad
int foo = 0; //这是一个注释示例
int foo = 0;// 这是一个注释示例
/*这是一个注释示例*/
/**这是一个注释示例*/
// good
if (foo == 0) {
}
// bad
if(foo == 0) {
}
// good
if (foo == 0) {
} else {
}
// bad
if(foo == 0) {
}else {
}
{
前必须加一个空格两个例外:
这也适用于以下“类运算符”符号:
类型界限中的&(
catch块中的管道符号(catch (FooException | BarException e)。
foreach语句中的分号。
// good
int i = 1 + 2;
// bad
int i = 1+2;
// good
if (!isOk) {
}
i++;
// bad
if (! isOk) {
}
i ++;
,
、:
、;
及右括号 )
后必须空格for (int i = 0; i < 100; i++) {
}
int i = (int) 3.5f;
new int[] {5, 6};
new int[] { 5, 6 };
几个经典的错误例子:
// good
void foo(int i) {}
// bad
void foo (int i) {}
// bad
void foo( int i) {}
// bad
void foo(int i ) {}
虽然增加了代码的可读性,但给维护带来问题。很多时候为了保持对齐,做了一些无用功。所以即使对于已经使用水平对齐的代码,我们也不需要去保持这种风格。
// good
int foo = 1;
String s = "foo";
// bad
int foo = 1;
String s = "foo";
类内连续的成员(字段,构造函数,方法,嵌套类,静态初始化块,实例初始化块等)之间,必须有一个空行
例外:两个连续字段之间的空行是可选的,用于字段的空行主要用来对字段进行逻辑分组。
既不鼓励也不反对这样做,视个人喜好而定。
这样做可增强代码的可读性
一个项目可以选择一行80个字符或100个字符的列限制。
我推荐 100 个字符。
任何一行如果超过这个字符数限制,必须换行。
例外:
不可能满足列限制的行(例如,Javadoc中的一个长URL,或是一个长的JSNI方法参考)。
package和import语句(见3.2节和3.3节)。
注释中那些可能被剪切并粘贴到shell中的命令行。
这条规则也适用于以下“类运算符”符号:点分隔符(.),类型界限中的&(
// good
int i = 1 + 2 + 3 + ... + 1000000
+ 1000001;
// bad
int i = 1 + 2 + 3 + ... + 1000000 +
1000001;
这条规则也适用于foreach语句中的分号
// good,虽然在这里没必要换行
int i =
100000000;
// bad
int i
= 100000000;
对于非空块,大括号遵循Kernighan和Ritchie风格 (Egyptian brackets):
Java的enum类有一些例外,在 Java 编程规范讲解。
// good
class Foo {
public void foo() {
if (condition) {
something();
} else {
other();
}
}
}
// bad
class
{
}
// bad
if (condition)
{
something();
}
else
{
other();
}
一个空的块状结构里什么也不包含,大括号可以简洁地写成{},不需要换行。
例外:如果它是一个多块语句的一部分(if/else 或 try/catch/finally) ,即使大括号内没内容,右大括号也要换行。
void doNothing() {}
不然会降低可读性。
// good
return count + 100;
// bad
return (count + 100);
// good
class Student {}
// bad
class Xuesheng {}
良好的命名应该能够顾名思义,不需要注释
// good
int studentCount;
// bad
int count; // 学生数量
不常见的缩略词会降低代码的可读性。尽量避免缩写,除非该缩写是众所周知的,如HTML、URL等等。
// good
class Foo {...}
// bad
class foo {...}
接口命名多以able或ible结尾
interface Runable {...}
interface Accessible {...}
class HashTest {
}
HashIntegrationTest {
}
也可以使用动词。
下划线可能出现在JUnit测试方法名称中用以分隔名称的逻辑组件。一个典型的模式是:test_,例如testPop_emptyStack。 并不存在唯一正确的方式来命名测试方法。
// good
void initView() {}
void updateData() {}
// not good
void init() {}
void update() {}
全部字母大写,用下划线分隔单词。
那,到底什么算是一个常量?
每个常量都是一个静态final字段,但不是所有静态final字段都是常量。在决定一个字段是否是一个常量时, 考虑它是否真的感觉像是一个常量。例如,如果任何一个该实例的观测状态是可变的,则它几乎肯定不会是一个常量。 只是永远不打算改变对象一般是不够的,它要真的一直不变才能将它示为常量。
// Constants
static final int NUMBER = 5;
static final ImmutableList NAMES = ImmutableList.of("Ed", "Ann");
static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable
static final SomeMutableType[] EMPTY_ARRAY = {};
enum SomeEnum { ENUM_CONSTANT }
// Not constants
static String nonFinal = "non-final";
final String nonStatic = "non-static";
static final Set mutableCollection = new HashSet();
static final ImmutableSet mutableElements = ImmutableSet.of(mutable);
static final Logger logger = Logger.getLogger(MyClass.getName());
static final String[] nonEmptyArray = {"these", "can", "change"};
这些名字通常是名词或名词短语。
代码中不允许出现直接硬编码的字面常量, 尤其是 重复出现 的硬编码。
你需要做的是把硬编码定义成常量。
如果常量只在一个类中用到,则在类中定义。否则可以在公共类中定义常量。
// good
final int MAX_COUNT = 99;
// bad
int int MAX_COUNT = 99;
这些名字通常是名词或名词短语。
参数应该避免用单个字符命名。
比起其它类型的名称,局部变量名可以有更为宽松的缩写。
虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。
即使局部变量是final和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。
允许在循环中使用单个字符的变量
避免在多重循环中同时使用 i 和 j 这种容易混淆的变量名
// good
boolean isReady = false;
// bad
boolean ready = false;
// good
Student student;
Student[] students;
List students;
// not good
List someStudent;
// bad
List list;
这里的注释不包括文档注释
//
而不是 /* */
// good
if (condition) {
// 这是一段注释
something();
}
// bad
if (condition) {
// 这是一段注释
something();
}
/* 这是一段很长很长很长...................很长
* 很长很长的注释。
*/
// 这也是一段很长很长很长...................很长
// 很长很长的注释。
/* 我不会告诉你这三种写法都是
* 可以的 */
这样做很可能给维护带来不必要的麻烦
// bad
/****************************************
* 这是一个
* 很漂亮但是没什么卵用注释
***************************************/
// bad
int foo; // 定义一个变量
// 创建一个Foo类
class Foo {
}
为什么这里是建议,而不是强制?因为很多时候,判断一句注释是不是废话还跟开发者水平有关。
举个不是很恰当的例子:
// bad
// 创建一个线程并执行
new Thread(new Runnable() {
public void run() {
something();
}
}).start();
稍微懂点 Java 的都知道这句注释是句废话,但对于一个刚入门,没接触过多线程的就不这么认为了,后面这条建议是对本条建议的补充说明:
不懂的去看相应的开发文档。
// good
int foo;
int foo2;
// bad
int foo, foo2;
// bar
var foo = 1,
foo2 = 2,
foo3 = 3;
不要在一个代码块的开头把局部变量一次性都声明了,而是在第一次需要使用它时才声明。 局部变量在声明时最好就进行初始化,或者声明后尽快进行初始化。
变量声明与使用的距离越远,代码的阅读与维护成本就越高。
从优化方面讲,这样做也是有好处的。
Java:
尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化
// good
if (i == 1) {
String str = "abc";
list.add(str);
}
// bad
String str = "abc";
if (i == 1) {
list.add(str);
}
// good
int foo;
int foo2;
// bad
int foo; int foo2;
连续赋值不仅影响可读性,而且很多时候容易出错。
// good
a = 1;
b = 1;
c = 1;
a = b = c = 1;
多级嵌套降低了代码的可读性。
大括号与if, else, for, do, while语句一起使用,即使只有一条语句(或是空),也应该把大括号写上。
// good
if (i > 0) {
i++;
}
// bad
if (i > 0)
i++;
// good
switch (i) {
case 1:
// ......
break;
case 2:
case 3:
case 4:
// ......
break;
}
// bad
if (i == 1) {
// ......
} else if (i == 1 || i == 2 || i == 3) {
// ......
}
// good
function getName() {
if (name) {
return name;
}
return 'unnamed';
}
// bad
function getName() {
if (name) {
return name;
} else {
return 'unnamed';
}
}
// bad
for (int i = 0; i < list.size(); i++) {
}
// good
for (int i = 0, int size = list.size(); i < size; i++) {
}
// good
int size = list.size();
for (int i = 0, ; i < size; i++) {
}
// good
for (int i = 0; i < 5; i++) {
for (int k = 0; k < 5000; k++) {
}
}
// bad
for (int k = 0; k < 5000; k++) {
for (int i = 0; i < 5; i++) {
}
}
太长的函数难以维护。
关于一个函数的规范行数没有统一标准,40行、60行也可以。
驼峰式命名法分大驼峰式命名法(UpperCamelCase)和小驼峰式命名法(lowerCamelCase)。 有时,我们有不只一种合理的方式将一个英语词组转换成驼峰形式,如缩略语或不寻常的结构(例如"IPv6"或"iOS")。Google指定了以下的转换方案。
名字从散文形式(prose form)开始:
把短语转换为纯ASCII码,并且移除任何单引号。例如:“Müller’s algorithm"将变成"Muellers algorithm”。
把这个结果切分成单词,在空格或其它标点符号(通常是连字符)处分割开。
推荐:如果某个单词已经有了常用的驼峰表示形式,按它的组成将它分割开(如"AdWords"将分割成"ad words")。 需要注意的是"iOS"并不是一个真正的驼峰表示形式,因此该推荐对它并不适用。
现在将所有字母都小写(包括缩写),然后将单词的第一个字母大写:最后将所有的单词连接起来得到一个标识符。
每个单词的第一个字母都大写,来得到大驼峰式命名。
除了第一个单词,每个单词的第一个字母都大写,来得到小驼峰式命名。
示例:
:
// good
XmlHttpRequest // XML HTTP request
newCustomerId // new customer ID
innerStopwatch // inner stopwatch
supportsIpv6OnIos // supports IPv6 on iOS
YouTubeImporter // YouTube importer
YoutubeImporter // 不推荐
// bad
XMLHTTPRequest
newCustomerID
innerStopWatch
supportsIPv6OnIOS
Note:在英语中,某些带有连字符的单词形式不唯一。例如:"nonempty"和"non-empty"都是正确的,因此方法名checkNonempty和checkNonEmpty也都是正确的。
使用分支能够有效地避免不同开发工作之间的相关干扰。
当需要开发新功能、修复bug、试验新的想法时,应该新建一个分支,待开发工作完成并测试后,再把工作分区合并到主分区上。
提交信息格式:
第一行:一句话简单总结一下你做的修改(别超过50个字)
第二行:空行(必须空行)
剩余行:详细描述。为什么要做这次改动?跟以前的实现有什么不一样?
一次提交(git commit)应该只包含相关的改动。比如说,修复两个不同的bug就应该分开来做两次提交。提交的改动越小(或越少),其他开发者理解起来就越容易;如果改动有问题,退回去也比较方便。Git有一个暂存区域(staging area)的概念,它还允许你暂存文件的某些部分,这更便于你创建非常细粒度的提交。
经常提交势必让你每次提交的东西都很少,也有助于你只提交相关的改动。并且,你还能更频繁地与别人共享代码。通过这种方式,所有人在集成代码时都会感觉更轻松,也就能避免一些不必要的冲突。相比之下,如果每次提交的东西很多、改动很大、时间间隔很长,那么在代码合并(merge)过程中产生的冲突就很难解决了。
把大功能的实现尽可能分解成更多的相对独立的小模块,每个小模块测试完成后提交修改,再开始下一模块的开发。
这样做能保证每次提交的内容高度相关,方便定为错误、解决合并冲突。
提交之前进行测试,测试完成并且没有错误才提交。
但当你把代码推送(git push)到服务器与别人共享时,这个问题就大了——在这之前,请务必测试你的代码!
Java编程必须遵守通用编程规范和本编程规范。
源文件结构
一个源文件包含(按顺序地):
许可证或版权信息(如有需要)
package语句
import语句
一个顶级类(只有一个)
以上每个部分之间用一个空行隔开。
// good
import java.util.Date;
import java.util.Map;
// bad
import java.util.*;
每个import语句独立成行。
import语句分为以下几组,按照这个顺序,每组由一个空行分隔:
所有的静态导入独立成组
com.google imports(仅当这个源文件是在com.google包下)
第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
java imports
javax imports
组内不空行,按字典序排列。
3.4.1 只有一个顶级类声明
每个顶级类都在一个与它同名的源文件中(当然,还包含.java后缀)。
例外:package-info.java,该文件中可没有package-info类。
新的方法不能总是习惯性地添加到类的结尾
推荐按照访问权限的大小分别排列属性和方法:
一个类的多个构造函数,或是多个同名方法,这些函数/方法应该按顺序出现在一起,中间不要放进其它函数/方法。
除非作者和reviewer都认为去掉小括号也不会使代码被误解,或是去掉小括号能让代码更易于阅读,否则我们不应该去掉小括号。 我们没有理由假设读者能记住整个Java运算符优先级表。
@Override
@Nullable
public String getNameIfPresent() { ... }
@Override public int hashCode() { ... }
@Partial @Mock DataLoader loader;
具体命名参照通用编程规范,这里不再赘述。
严格来说,遵守本规范的Java代码中,标识符只有三种形式:
UpperCamelCase、lowerCamelCase 和 CONSTANT_CASE。
除了常量外,所有标识符只由字母和数字组成。
// good
int count;
String name;
// bad
int iCount; // 常用 i 来表示整形
String sName; // 常用 s 来表示字符串
String nName; // 常用 m 来表示 数据成员
TODO 这条建议是否应该改成强制?
// good?
Button btnClose; // 关闭按钮
// good
com.google.util.imagetool
// bad
com.google.util.imageTool
com.google.util.image_tool
一、二级包名通常是域名的反写,因为域名不会重复。
如果有域名则采用域名的反写:
chenjianhang.com => com.chenjianhang
abc.net => net.abc
如果没有域名:
一级包名为域名后缀,如com、net、cn、me等。
公司的项目通常是com,个人的项目可以选择me,团队的项目可以采用team,其他的也可以。
二级包名是公司、组织、机构、团队或者个人的名称。
三级包名是项目名。
四级包名为模块名或层级名。
com.baidu.tieba.view
com.sina.weibo.xxx
只允许下面这两种形式
当整个 Javadoc 块能容纳于一行,且没有 Javadoc 标记 @XXX
时,可以使用单行形式。
/**
* 这是一个多行的 Javadoc 注释
* 这是一个多行的 Javadoc 注释
*/
public void foo() { ... }
/** 这是一个单行的 Javadoc 注释 */
7.1.2 段落
空行(即,只包含最左侧星号的行)会出现在段落之间和Javadoc标记(@XXX)之前(如果有的话)。 除了第一个段落,每个段落第一个单词前都有标签
,并且它和第一个单词间没有空格。
7.1.3 Javadoc标记
标准的Javadoc标记按以下顺序出现:@param, @return, @throws, @deprecated, 前面这4种标记如果出现,描述都不能为空。 当描述无法在一行中容纳,连续行需要至少再缩进4个空格。
7.2 摘要片段
每个类或成员的Javadoc以一个简短的摘要片段开始。这个片段是非常重要的,在某些情况下,它是唯一出现的文本,比如在类和方法索引中。
这只是一个小片段,可以是一个名词短语或动词短语,但不是一个完整的句子。它不会以A {@code Foo} is a…或This method returns…开头, 它也不会是一个完整的祈使句,如Save the record…。然而,由于开头大写及被加了标点,它看起来就像是个完整的句子。
Tip:一个常见的错误是把简单的Javadoc写成/** @return the customer ID /,这是不正确的。它应该写成/* Returns the customer ID. */。
// bad
/** 返回学生数量 */
public int getStudentCount() {
return studentCount;
}
当然,如果方法名中有比较偏僻的英文词汇,或者有一些相关信息是需要读者了解的, Javadoc 注释不应该省略
/** 返回符合规范的名称 */
public String getCanonicalName() {
}
boolean isOk = true;
// good
if (isOk) {
}
// good
if (!isOk) {
}
// bad
if (isOk == true) {
}
if (isOk == false) {
}
// good
i++;
i--;
// bad
i = i + 1;
i = i - 1;
// bad
i = i++; // Java 和 C 执行结果是不一样的
if (c++ = d++) {
...
}
// good
a = b + c;
d = a + r;
// bad
d = (a = b + c) + r;
if ((a == b) && (c == d)) // good
if (a == b && c == d) // 不反对
// good
i /= 4;
// bad
i = i >> 2;
// good
return booleanExpression;
// bad
if (booleanExpression) {
return true;
} else {
return false;
}
?
前面有二元运算符,条件表达式应该用括号扩起来// good
i = (x >= 0) ? x : -x;
// bad
i = x >=0 ? x : -x;
// good
return (condition ? x : y);
// bad
if (condition) {
return x;
}
return y;
// god
i = (x >= 0) ? x : -x;
// bad
if (x >= 0) {
i = x;
} else {
i = -x;
}
所有 case 语句和 default 语句必须满足三条规范之一:
case 3
。case 2
。case 1
。switch (input) {
case 1: // 注意case 1 与 case 2 之间不能有空行
case 2:
prepareOneOrTwo();
// 不用break
case 3:
handleOneTwoOrThree();
break;
default:
handleLargeNumber(input);
break;
}
即使 default 语句什么代码也不包含,你也必须这么做
new int[] { 0, 1, 2, 3 };
new int[] {
0, 1, 2, 3
};
new int[] {
0,
1,
2,
3
};
new int[] {
0, 1,
2, 3
};
new int[]
{0, 1, 2, 3};
中括号是类型的一部分。
// good
String[] args;
// bad
String args[];
推荐的顺序:
public protected private abstract static final transient volatile synchronized native strictfp
枚举常量间用逗号隔开,换行可选。
没有方法和文档的枚举类可写成数组初始化的格式:
private enum Suit { CLUBS, HEARTS, SPADES, DIAMONDS }
由于枚举类也是一个类,因此所有适用于其它类的格式规则也适用于枚举类。
类似的还有 XXX 和 FIXME。
// good
// TODO 这是一句必要的说明
// bad
//TODO 这是一句必要的说明
// bad
// TODO Auto-generated method stub
// TODO 这里需要添加XXX功能
// XXX 建议这里使用的排序改成冒泡排序,效率更高
// FIXME 当用户没有输入时,这里有bug,需加个判断
常见的处理方式就是打印日志。
如果它被认为是不可能的,则把它当作一个AssertionError重新抛出。
如果它确实是不需要在catch块中做任何响应,需要做注释加以说明。
try {
int i = Integer.parseInt(response);
return handleNumericResponse(i);
} catch (NumberFormatException ok) {
// it's not numeric; that's fine, just continue
}
return handleTextResponse(response);
例外:在测试中,如果一个捕获的异常被命名为expected,则它可以被不加注释地忽略。下面是一种非常常见的情形,用以确保所测试的方法会抛出一个期望中的异常, 因此在这里就没有必要加注释。
try {
emptyStack.pop();
fail();
} catch (NoSuchElementException expected) {
}
class Foo {
public static void foo() {
}
}
// good
Foo.foo();
// bad
new Foo().foo();
如果你觉得非要重载Object.finalize不可,请先仔细阅读和理解《Effective Java》 第7条:“Avoid Finalizers”,然后不要使用它。
@Deprecated
避免频繁地通过 +
进行字符串拼接
**StringBuffer **
线程池
举个不恰当的例子:
// good
class Foo {
String name;
void sayHello() {
System.out.println("My name is" + name);
}
}
// bad
class Foo {
static String name;
static void sayHello() {
System.out.println("My name is" + name);
}
}
不可否认的是,很多刚学 java 的新手常常这么做。
一是为了简洁、二是为了效率
// good
class Foo {
String name;
public String getName() {
return name;
}
void sayHello() {
System.out.println("My name is" + name);
}
}
// bad
class Foo {
String name;
public String getName() {
return name;
}
void sayHello() {
System.out.println("My name is" + getName());
}
}
为了简洁。
如果和参数冲突,才加 this。
// good
class Foo {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// bad
class Foo {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
如果不需要访问某对象的字段,将方法设置为静态,调用会加速15%到20%。这也是一种好的做法,因为你可以从方法声明中看出调用该方法不需要更新此对象的状态。
// good
static final String DATABASE_NAME = "foo";
// bad
final String DATABASE_NAME = "foo";
anim // animation
avg // average
bg // background
buf // buffer
ctrl // control
del // delete
doc // document
err // error
esc // escape
inc // increment
info // infomation
init // initial
ic // icon
img // image
len // length
lib // library
msg // message
pwd // password
pos // position
srv // server
str // string
tmp // temp
lambda 表达式可读性低。
1 前言
2 代码风格
3 设计规范
3.1 表
4 使用规范
4.1 查询
本规范适用于 MYSQL 数据库设计,对其他数据库设计也有参考价值。
为了统一规范、易于辨识以及减少传输量。
当库名、表名、字段名等属性含有保留字时,SQL语句必须用反引号引用属性名称,这将使得SQL语句书写、SHELL脚本中变量的转义等变得非常复杂。
常见关键字,如:name,time ,datetime password 等。
简短、有意义、易于理解的英文单词。
谨慎使用缩写。
必要的注释。
表的前缀一般用系统或模块的名称缩写,而不是没有意义的t(table)。
例如 tmp_test01_20130704。
例如 bak_test01_20130704。
例如 UI_UserID(表tb_user_info-list)
系统中属于是业务内的编号字段,代表一定业务信息,建议字段命名为code , 如工作单编号wf_code .
不要在数据库表字段(列名)命名时重复表名,可以使用表名首字母(不包含数据库表名前缀)
视图的命名请遵循以下命名规范:UV _ + 系统模块缩写(与表前缀类似)+_ + 功能标识 + 代表视图查询的主要表名(不带前缀)或功能的英文单词或英文单词缩写。
如果一个视图只对一个表进行查询,建议视图的名称就用视图所查询的表的表名(不带前缀)。这样有利于根据表名找到相应的视图。
注:UV是userView缩写
存贮过程的命名请遵循以下命名规范:USP_ + 系统模块缩写(与表前缀类似)+_ + 功能标识 + 代表存贮过程操作的主要表名(不带前缀)或功能的英文单词或英文单词缩写。
如果一个存贮过程只对一个表进行操作,建议存贮过程的名称就用存贮过程所操作的表的表名(不带前缀)。这样有利于根据表名找到相应的存贮过程。例如:
用于新增的存贮过程USP_MESSAGE_Add_Model
用于修改的存贮过程USP_ MESSAGE_Upt_Model
用于删除的存贮过程USP_ MESSAGE_Del_ Modele
注:USP是user stored procedure
INNODB引擎是MySQL5.5版本以后的默认引擘,支持事务、行级锁,有更好的数据恢复能力、更好的并发性能,同时对多核、大内存、SSD等硬件支持更好,支持数据热备份等,因此INNODB相比MyISAM有明显优势。
同样的字节数,非负存储的数值范围更大。如TINYINT有符号为 -128-127,无符号为0-255。
ENUM类型在需要修改或增加枚举值时,需要在线DDL,成本较大;ENUM列值如果含有数字类型,可能会引起默认值混淆。
原因:
建议用 0、空串、当前时间或特殊值代替 NULL 值。
INT 类型占存储空间小,速度快。
例如:用UNSIGNED INT 而不是 char(15) 存储 IPv4。
通过MySQL函数inet_ntoa和inet_aton来进行转化IPv4。
SELECT INET_ATON('209.207.224.40'); 3520061480
SELECT INET_NTOA(3520061480); 209.207.224.40
整型:
建议:
浮点型:
建议:
字符型:
建议:
时间型:
建议:
优先使用enum或set
例如:sex
enum (‘F’, ‘M’)。
(4)字段的描述
a.字段必须填写描述信息(注释)
(5)加索引规则
a.表建好后数据库自动为表生成一个索引(为
自动增长的列生成唯一索引),如果在对这列添加索引,数据库会给一个警告,内容大概是,已经为这列添加了索引,建议修改索引名称和自动增长列名保持一致,为了方便使用。
b.如果在添加索引时,建议索引名称和数据库列名保持一致,为了方便使用
c.如果字段事实上是与其它表的关键字相关联而未设计为外键引用,需建索引。
d.如果字段与其它表的字段相关联,需建索引。
e.如果字段需做模糊查询之外的条件查询,需建索引。
f.除了主关键字允许建立簇索引外,其它字段所建索引必须为非簇索引。
主键是不可修改的,当业务变更时,与业务有关的主键会造成很多问题。比如你用int型做文章的id,但是如果在未来某一天文章数超过了无符号整形的最大值,你将没法将主键修改成bigint。或者为了给用户起一个唯一id用了自增主键,但是如果未来有其他的项目用户要合并进来,他也是这么做的。这时候为了区分不同的项目可能要在这个用户id前加一个前缀,这时候也没法修改主键的值。主键之所以叫做主键就是到什么时候都不能改,所以最好的方案就是使用自增数字id做主键,并且不要给这个主键赋予一个业务相关的意义。
根据数据量,可以采用自增 INT 主键或者其他类型的主键。
典型错误例子:
使用学号作为学生表的主键;使用用户名作为用户表的主键。
应尽量避免在库表中引入与业务逻辑相关的主键关系。
复合主键的引入,很大程度上意味着业务逻辑已经侵入到数据存储逻辑之中。
给原来的“复合主键”做唯一索引。
如果是非互联网应用,数据量不是很大,优先考虑使用自增主键。
互联网等数据量大的应用,因为要分库分表,不推荐使用自增主键。
原因:
制定合适的主键生成策略,保证主键的唯一性,有序性和简短。
自增 id 作为主键(或者是改用有特定顺序的键),uuid作为唯一键。
改善查询、减慢更新;
索引一定不是越多越好能不加就不加,要加的一定得加;
覆盖记录条数过多不适合建索引,例如“性别”;
例如“性别”。
大部分场景下,低基数列上建立索引的精确查找,相对于不建立索引的全表扫描没有任何优势,而且增大了IO负担。
重复索引增加维护负担、占用磁盘空间,且没有任何好处。
在视图中必须说明以下内容:
建议:在数据库中创建一个文本文件保存创建脚本
在存贮过程中必须说明以下内容:
修改记录需包含修改顺序号、修改者、修改日期、修改原因,修改时不能直接在原来的代码上修改,也不能删除原来的代码,只能先将原来的代码注释掉,再重新增加正确的代码。修改顺序号的形式为:log1,log2,log3。。。,根据修改次数顺序增加,同时在注释掉的原来的代码块和新增的正确代码块前后注明修改顺序号。
5. 对存贮过程各参数及变量的中文注解。
建议:在数据库中创建一个文本文件保存创建脚本
采用加密字符串存储密码,并保证密码不可解密,同时采用随机字符串加盐保证密码安全。防止数据库数据被公司内部人员或黑客获取后,采用字典攻击等方式暴力破解用户密码。
select *
SELECT 只获取必要的字段。
消耗cpu,io,内存,带宽;这种程序不具有扩展性;
减少网络带宽消耗;
能有效利用覆盖索引;
表结构变更对程序基本无影响。
IN是范围查找,MySQL内部会对IN的列表值进行排序后查找,比OR效率更高。
or的效率是n级别;
in的效率是log(n)级别;
/* good */
select id from t where phone in ('159', '136');
/* bad */
select id from t where phone='159' or phone='136';
MySQL 的索引合并很弱智。
/* good */
select id from t where phone='159'
union
select id from t where name='jonh'
/* bad */
select id from t where phone = '159' or name = 'john';
数据库通常存储的是文件的路径(多用相对路径)。
高并发环境不要使用外键,太容易产生死锁,应由程序保证约束。
int型不超过1000w,含char则不超过500w。
mysql在处理大表(char的表>500W行,或int表>1000W)时,性能就开始明显降低,所以要采用不同的方式控制单表容量
A:根据数据冷热,对数据分级存储,历史归档
B:采用分库/分表/分区表,横向拆分控制单表容量
C:对于OLTP系统,控制单事务的资源消耗,遇到大事务可以拆解,采用化整为零模式,避免特例影响大众
拆分复杂 SQL 为多个小 SQL,避免大事务,减少锁时间。
bad case:
上传图片事务
确保数据更新的同时冗余字段也被更新。
将大字段、访问频率低的字段拆分到单独的表中存储,分离冷热数据。
有利于有效利用缓存,防止读入无用的冷数据,较少磁盘IO,同时保证热数据常驻内存提高缓存命中率。
不在索引列上进行数学运算或函数运算。
cpu计算务必移至业务层。