1 目的
本规范的主要目的是希望规范数据库设计与开发,尽量避免由于数据库设计与开发不当而产生的麻烦;同时好的规范,在执行的时候可以培养出好的习惯,好的习惯是软件质量的很好保证。
2 适用范围
本规划的适用人员范围包括涉及数据库设计与开发的相关技术人员。
3 术语约定
本规范采用以下术语描述:
★规则:也称为强规范是编程时必须强制遵守的原则
★建议:编程时必须加以考虑的原则
★说明:对此规则或建议进行必要的解释
★示例:对此规则或建议从正、反两个方面给出
4 规范及建议
4.1 书写规范
4.1.1 SQL书写规范
规则1: 数据库代码中,关键字大写,其他内容小写;
示例:
如下代码不符合规范:(关键字未大写)
select last_name ,job_id
from employees;
如下代码符合规范:
SELECT last_name, job_id
FROM employees;
规则2:程序块应采用缩进风格书写,保证代码可读,风格一致,缩进格数统一为4格;
规则3:代码中需要空位时,统一采用英文空格键输入,不允许用TAB键 产生空位;
说明:不同的编辑器对TAB的空位格数设置不一致,会导致使用TAB键产生空位的代码格式混乱;
规则4:同一条语句占用多行时,每一行的开始应是关键字, 且关键字应和第一行左对齐,如确实不能从关键字分行,则分行处应对其上一行被分行的同类代码的最左边;
示例:
如下代码不符合规范(分行书写时,其余行未和第一行左对齐)
SELECT last_name,
job_id
FROM employees;
如下代码也不符合规范(分行时,不是从关键字分行)
SELECT last_name,
job_id FROM employees;
如下代码符合规范
SELECT last_name, job_id
FROM employees;
如下代码符合规范
SELECT last_name,
first_name,
job_id
FROM employees;
规则5:查询数据时,尽量不使用SELECT *,而是给出明确的字段,但该规则不包括SELECT COUNT(*)语 句;
示例
如下语句不符合规范(SELECT操作未给出字段)
SELECT *
FROM employees;
如下语句符合规范
SELECT last_name, first_name
FROM employees;
规则6:INSERT语句应该给出字段列表;
示例
如下语句不符合规范(INSERT操作未给出字段名称)
INSERT INTO employees
VALUES
(
'GUO',
'DAVID',
100
);
如下语句符合规范
INSERT INTO employees
(
last_name,
first_name,
job_id
)
VALUES
(
'GUO',
'DAVID',
100
);
规则7:从表中同一笔记录中获取记录的字段值,须使用一SQL语句得到,不允许分多条SQL语句;
示例
如下语句不符合规范(从同一个表中取出记录,分成两条语句分别扫描)
UPDATE employees_new
SET last_name=
(
SELECT last_name
FROM employees
WHERE job_id = 100
)
WHERE job_id = 100;
UPDATE employees_new
SET first_name =
(
SELECT first_name
FROM employees
WHERE job_id = 100
)
WHERE job_id = 100;
如下语句符合规范
UPDATE employees_new
SET first_name =
(
SELECT last_name
FROM employees
WHERE job_id = 100
),
last_name =
(
SELECT first_name
FROM employees
WHERE job_id = 100
)
WHERE job_id = 100;
规则8:SQL语句中的逗号后面应增加一个空格,以使得代码清晰;
示例
如下代码不符合规范(逗号后面没有空格)
SELECT last_name,job_id
FROM employees;
如下代码符合规则
SELECT last_name, job_id
FROM employees;
规则9:不允许将SQL语句写成一行,再短的SQL也应该在谓词处分行;
示例
如下代码不符合规范(未在谓词部分进行分行)
SELECT last_name, job_id FROM employees WHERE job_id = 1;
如下代码符合规范
SELECT last_name, job_id
FROM employees
WHERE job_id = 1;
规则10:运算符以及比较符左边或者右边只要不是括号,则空一格;
示例
如下代码不符合规范(运算符没有空格)
SELECT CURRENT_DATE+INTERVAL 1 DAY
FROM dual;
如下代码符合规范
SLEECT CURRENT_DATE + (INTERVAL 1 DAY)
FROM dual;
规则11:不同类型的操作符混合使用时,应使用括号明确的表达运算的先后关系;
示例
如下代码不符合规范(运算优先级关系易混淆)
SELECT a*b/c+d*e
FROM dual;
如下代码符合规范
SELECT ((a * b) / c) + (d * e)
FROM dual;
规则12:任何SQL书写单行不得超过120字符(含左边的缩进);
建议1:对于INSERT…VALUES和UPDATE语句,一行写一个字段,每个字段相对于INSERT语句空4格,字段后面紧跟注释(注释语句左对齐),VALUES和INSERT左对齐,左括号和右括号与INSERT、VALUES左 对齐;
示例:
如下代码不符合建议(字段未和INSERT语句空格)
INSERT INTO sm_user
(
user_id, --用户ID,主键
user_name, --用户名
login_name --登录名
)
VALUES
(
p_user_id,
p_user_name,
p_login_name
);
如下代码符合建议
INSERT INTO sm_user
(
user_id, --用户ID,主键
user_name, --用户名
login_name --登录名
)
VALUES
(
p_user_id,
p_user_name,
p_login_name
);
建议2:INSERT…SELECT 语句时,应使每行的字段顺序对应,以每行最多不超过4个字段,以方便代码阅读,括号的内容另起一行缩进4格开始书写,关键字单词左对齐,左括号、右括号另起一行与左对齐;
示例
如下代码不符合建议(字段未和括号分行)
INSERT INTO sm_duty_bak(duty_id, duty_name, created_by, creation_date,
last_updated_by, last_update_date, disable_date)
SELECT duty_id, duty_name, created_by, creation_date,
last_updated_by, last_update_date, disable_date
FROM sm_duty
WHERE duty_id=88;
如下代码符合建议
INSERT INTO sm_duty_bak
(
duty_id, duty_name, created_by, creation_date,
last_updated_by, last_update_date, disable_date
)
SELECT
duty_id, duty_name, created_by, creation_date,
last_updated_by, last_update_date, disable_date
FROM sm_duty
WHERE duty_id = 88;
说明:
1.SELECT 语句中每行的字段应与INSERT 语句对应。
2.INSERT 语句中换行的字段名应缩进并与上一行的第一个字段名对齐。
3.SELECT 语句中换行的字段名应缩进并与上一行的第一个字段名对齐。
4.1.2 存储过程书写规范
规则1:不允许将多行语句书写在同一行;
示例
如下代码不符合规范(将两行定义书写在同一行)
SET v_count = 1; SET v_creation_date = CURRENT_DATE;
如下代码符合规范
SET v_count = 1;
SET v_creation_date = CURRENT_DATE;
规则2:相对独立的程序块之间应加空行;
示例
如下代码不符合规范(变量定义和程序段之间无空行)
SET v_duty_id = 1;
IF (v_disabled_date > v_current_date) THEN
SELECT duty_name
into v_duty_name
FROM sm_duty
WHERE duty_id = :duty_id;
…
END IF;
如下代码符合规范
SET v_duty_id = 1;
IF (v_disabled_date > v_current_date) THEN
SELECT duty_name
into v_duty_name
FROM sm_duty
WHERE duty_id = :duty_id;
…
END IF;
规则3:当一个SQL 语句中涉及到多个表时,始终使用别名来限定字段名,这使其它人阅读起来更方便,避免了含义模糊的引用,其中能够通过别名清晰地判断出表名;
说明 : 别名命名时,尽量避免使用无意义的代号a、b 、c… , 而应该有意义( 如表mtl_system_items_b 对应别名为msi,po_headers_all 别名对应为pha)。
示例
如下语句不符合规范(未使用有明确含义的表别名)
SELECT a.wip_entity_name, a.wip_entity_id, a.date_released
FROM wip.wip_entities b,
wip.wip_discrete_jobs a
WHERE b.wip_entity_id = a.wip_entity_id
AND a.status_type = 3
如下语句符合规范
SELECT wdj.we_entity_name, wdj.wip_entity_id, wdj.date_released
FROM wip.wip_entities we,
wip.wip_discrete_jobs wdj
WHERE we.wip_entity_id = wdj.wip_entity_id
AND we.status_type = 3
规则4:确保变量/参数的类型和长度与表数据字段的类型和长度相匹配;
说明:如果与表数据列宽度不匹配,则当较宽或较大的数据传进来时会产生运行异常。
示例
如下代码不符合规范(假定表wap_user的字段user_name的定义为VARCHAR(10))
CREATE PROCEDURE ps_add()
BEGIN
DECLARE v_user_name VARCHAR(15);
UPDATE wap_user
SET user_name = v_user_name
WHERE sky_id = 100;
END;
如下代码符合规范
CREATE PROCEDURE ps_add()
BEGIN
DECLARE v_user_name VARCHAR(10);
UPDATE wap_user
SET user_name = v_user_name
WHERE sky_id = 100;
END;
规则5:存储过程代码块必须有注释;
建议1:减少控制语句的判断次数,比如在ELSE(IF…ELSE) 语句中,尽量将尽快能检测到结果的判断放在前面;
示例
如下语句不符合规范(假定v_count=1的条件大多数情况会满足)
IF (v_count = 0) THEN
NULL;
ELSEIF (v_count = 1) THEN
NULL;
END IF;
如下语句符合规范(假定v_count=1的条件大多数情况会满足)
IF (v_count = 1) THEN
NULL;
ELSEIF (v_count = 0) THEN
NULL;
END IF;
建议2:尽量避免使用嵌套的IF语句,在这种情况下应使用多个IF语句来判断其可能性;
示例
如下语句不符合规范(使用了嵌套的IF语句来进行判定)
IF v_count = 0 THEN
IF v_flag = 0 THEN
NULL;
ELSE
NULL;
END IF;
ELSE v_count = 1 THEN
IF v_flag = 0 THEN
NULL;
ELSE
NULL;
END IF;
END IF;
如下语句符合规范
IF (v_count = 0) AND (v_flag = 0) THEN
NULL;
ELSEIF (v_count = 0 ) AND (v_flag = 1) THEN
NULL;
ELSEIF (v_count = 1) AND (v_flag = 0) THEN
NULL;
ELSEIF (v_count = 1) AND (v_flag = 1) THEN
NULL;
END IF;
建议3:存储过程、函数、触发器、程序块中定义的变量和输入、输出参数在命名上有所区分;
说明:
用'v_ '开头代表程序块中定义的普通变量。
用'p_ '开头代表输入参数变量。
用'x_ '开头代表输入输出或输出参数变量。
用'cur_'开头代表游标变量。存放游标记录集。
4.2 对象命名规范
4.2.1 通用规则
规则1:任何数据库对象的命名,不得使用汉字;
示例
如下语句不符合规范(表明和字段名使用了汉字)
CREATE TABLE 用户
(
用户名 VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
规则2:库名,表名,字段名不得超过30个字符,用户名不得超过16个字符;
库名,表名,字段名最多支持64个字符,为了统一规范、易于辨识以及减少传输量,必须不超过30个字符。
示例
如下语句不符合规范(表命名达到65位长度)(修改)
CREATE TABLE wap_user_tel_number_region_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句符合规范
CREATE TABLE wap_user_tel_number_region
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
规则3:用户对象命名应全部为小写,使用下划线“_”分割;
说明:由于linux操作系统上的文件名是区分大小写的,所以MySQL表名是区分大小写的。
示例
如下语句不符合规范(表名应全部为小写)
CREATE TABLE Wap_user_tel_number_region
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句符合规范
CREATE TABLE wap_user_tel_number_region
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
规则4:命名应使用富有意义的英文,禁止使用拼音首字母, 一般情况下不建议使用拼音命名;
示例
如下语句不符合规范(表名使用了中文且字段使用了拼音首字母简写)
CREATE TABLE wap_yonghu
(
yhm VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
规则5:命名不得使用数据库保留字;
说明:使用了数据库保留字,会导致需要访问该对象时,需要代码做特别的转换才能访问
示例
如下代码不符合规范(假定user为数据库保留字)
CREATE TABLE wap_user
(
USER VARCHAR(100),
pass_word VARCHAR(16)
);
如下代码符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
4.2.2 表
规则1:同类业务的表,以相同的表示该类业务的英文开头;
说明:同类业务的表以相同的英文开头,在逻辑上清晰,且可避免维护过程中对该类表的误操作
示例
如下语句不符合规范(假定表wap_user和表user_login_log都属于wap类业务)
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
CREATE TABLE user_login_log
(
user_name VARCHAR(100),
login_date DATE
);
如下语句符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
CREATE TABLE wap_user_login_log
(
user_name VARCHAR(100),
login_date DATE
);
说明:各子系统不用加子系统名称前缀,如POS系统的表不用都加pos_前缀,如果遇到需要同步其他系统的表的表名与本系统的表名相同时,用子系统名称做后缀的形式重命名其他子系统表名,如POS系统需要同步MDM表bill_item_dtl而POS系统也存在这样的表名,则把MDM的表名重新命名为bill_item_dtl_mdm。
规则2:同类表,如果按照时间不同建立的表,后缀格式一般情况下应为’_YYYY[MM[DD]]’格式;
示例
如下语句不符合规范(将年份2010简写为10,导致含义模糊)
CREATE TABLE wap_user_login_1004
(
user_name VARCHAR(100),
login_date date
);
CREATE TABLE wap_user_login_1005
(
user_name VARCHAR(100),
login_date DATE
);
如下语句符合规范
CREATE TABLE wap_user_login_201004
(
user_name VARCHAR(100),
login_date DATE
);
CREATE TABLE wap_user_login_201005
(
user_name VARCHAR(100),
login_date DATE
);
4.2.3 字段
规则1:字段命名应具有含义,能反映该字段存储的内容,且字段应增加字段备注;
示例
如下语句不符合规范(假定存储的字段为用户名和密码,如下的字段名毫无意义也没有备注)
CREATE TABLE wap_user
(
col1 VARCHAR(100),
col2 VARCHAR(16)
);
如下语句符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句是使用了无意义字段名,但增加了字段说明,不作为推荐方法,但确实字段名无法表述含义时,必须使用该方法;
CREATE TABLE wap_user
(
col1 VARCHAR(100) comment 'username',
pass_word VARCHAR(16) comment 'password'
);
规则2:同种用途的字段,在所有表中,应保持有同样的字段类型和字段长度,并尽量保持一致的字段命名;
示例
如下语句不符合规范(字段user_name在两个有业务关系的表中字段长度不一致,易导致业务接口冲突)
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
CREATE TABLE wap_user_login_log
(
user_name VARCHAR(80),
login_date DATE
);
如下语句符合规范
CREATE TABLE wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR2(16)
);
CREATE TABLE wap_user_login_log
(
user_name VARCHAR(100),
login_date DATE
);
以下是建议的公共字段名称及类型
create_user | VARCHAR(32) | 建档人 |
create_time | datetime | 创建时间 |
update_user | VARCHAR(32) | 更新人 |
update_time | datetime | 更新时间 |
remark | VARCHAR(255) | 备注 |
contact_name | VARCHAR(32) | 联系人 |
tel | VARCHAR(20) | 电话号码 |
mob | VARCHAR(20) | 手机号码 |
address | VARCHAR(100) | 联系地址 |
zip_code | VARCHAR(10) | 邮编 |
identity_card | VARCHAR(25) | 身份证号 |
fax | VARCHAR(20) | 传真 |
VARCHAR(64) | 电邮 |
建议1: 字段名建议不要用JAVA关键字来命名,;
4.2.4 主键
规则1:涉及到要做分库分表的表用有序UUID做主键,UUID主键类型选择CHAR(32);
规则2:不涉及分库分表的表选用自增长ID做主键,主键类型使用unsigned int或unsigned big int;
规则3:主键无特别要求的,字段名统一定义为 id;
4.2.4 外键
规则1:外键名应以”fk_”开头,后接表名;
示例
如下语句不符合规范(外键名未以fk_开头)
alter table wap_user_login_log
add constraint wap_user_login_log_f foreign key(user_name) REFERENCES tb_user_name(user_name)
如下语句符合规范
ALTER TABLE wap_user_login_log
ADD CONSTRAINT fk_wap_user_login_log FOREIGN KEY(user_name) REFERENCES tb_user_name(user_name)
规则2:不同的表的外键,如果引用的是相同表的相同字段,则外键字段名及类型应保持一致;
4.2.5 索引
规则1:唯一索引应以”uk_”+”表名_”+”字段名”命名;
示例
如下语句不符合规范(唯一索引未以uk_开头)
ALTER TABLE wap_user
ADD UNIQUE wap_user_username_u (username)
如下语句符合规范
ALTER TABLE wap_user
ADD UNIQUE uk_wap_user_username (username)
规则2:普通索引应以”idx_”+”表名_”+“字段名”命名;
示例
如下语句不符合规范(不符合索引命名规范)
ALTER TABLE wap_user
ADD INDEX wap_user_user_id_idx (user_id)
如下语句符合规范
ALTER TABLE wap_user
ADD INDEX idx_wap_user_user_id (user_id)
规则3:全文索引索引应以”fullidx_”+”表名_”+“字段名”命名;
4.2.6 视图
规则1:视图命名应以“v_”+“表名[_表名[_表名]]”命名,如果表名过多可以用“v_”+“功能描述”来命名;
示例
如下语句不符合规范(视图和表是不可以同名的,如下语句会引起错误且不符合规范)
CREATE VIEW wap_user
AS
SELECT first_name, last_name, job_id
FROM wap_user;
如下语句符合规范
CREATE VIEW v_wap_user
AS
SELECT first_name, last_name, job_id
FROM wap_user;
4.2.7 函数
规则1:函数命名以”func_”开头,后接函数的功能;
示例
如下语句不符合规范(未以func_开头)
CREATE FUNCTION get_money
BEGIN
……
END;
如下语句符合规范
CREATE FUNCTION func_get_money
BEGIN
……
END;
4.2.8 存储过程
规则1:存储过程以“proc_”开头,后接功能描述;
示例
如下语句不符合规范(未以proc_开头)
CREATE PROCEDURE update_user
BEGIN
……
END;
如下语句符合规范
CREATE PROCEDURE proc_update_user
BEGIN
……
END;
4.2.9 触发器
规则1:触发器以“trig_”+表名+“_ins/del/upd”+”_before/after”命名;
示例
如下语句不符合规范(未遵循命名规范)
CREATE TRIGGER trigger1
AFTER DELETE ON wap_user
BEGIN
……
END;
如下语句符合规范
CREATE TRIGGER trig_wap_user_del_after
AFTER DELETE ON wap_user
BEGIN
……
END;
4.2.10 临时表
规则1:临时表以“tmp_”开头,后接功能描述;
示例
如下语句不符合规范
CREATE TEMPORARY TABLE tab_tmp1
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
如下语句符合规范
CREATE TEMPORARY TABLE tmp_wap_user
(
user_name VARCHAR(100),
pass_word VARCHAR(16)
);
规则2:如果是在上线/割接中被重命名的表,命名应是原表名+“_YYYYMMDD”;
示例
如下语句不符合规范(临时表以old结尾,而非日期结尾)
RENAME TABLE wap_user TO wap_user_old;
如下语句符合规范
RENAME TABLE wap_user TO wap_user_20100416;
4.2.11 用户及数据库名
规则1: 数据库名:retail_前缀+模块名,如POS系统的数据库名为retail_pos,MDM的数据库名为retail_mdm,用户名与数据库名尽量一致;
示例
如下语句符合规范
retail_pos pos系统
retail_mps 营促销系统
retail_oc 订单中心
retail_pms 采购管理系统
retail_mdm mdm
retail_gms 货品管理系统
retail_fms 财务管理系统
4.3对象设计规范
4.3.1 表设计
规则1:数据库设计文档中,必须包含表数据保留时间;
规则2:数据库设计文档中,必须包含表在最大保留时间下的数据量;
规则3:数据库设计文档中,必须包含表的读写频率;
规则4:和其他表有关联的表,和其他表功能一致的字段类型以及长度,尽量使用相同的列名;
规则5: 每个表应设计一个主键;
规则6:数据库字符集,表字符集,字段字符集统一选用UTF8字符集,校对规则统一使用大小写敏感的utf8_bin;
规则7:表引擎选用INNODB引擎;
规则8:必须要有表的注释,用于描述表的功能;
建议1:对于需要同步到数据仓库的表,原则上必须包含同步频率以及同步机制;
建议2:历史表后缀建议用“_hist”;
4.3.2 字段
规则1:字段必须要有注释信息,如果字段的值是有限的(如状态值只有“有效”、“无效”,如性别只有“男”、“女”等)必须在字段注释中对每个值表达的意思进行描述;
规则2:定长字符列使用CHAR类型, 不定长字符型使用VARCHAR类型;
规则3:日期字段只需要表达年月日的选用DATE类型,需要表达年月日时分秒的字段选用DATETIME类或TIMESTAMP类型,但请注意各自能表达的范围以及TIMESTAMP的时区特性;
说明:MySQL中的DATETIME对应ORACLE的DATE类型,而MySQL的DATE类型只是ORACLE DATE类型的年月日部分不包括时分秒部分,MySQL TIME类型是ORACLE DATE 类型的时分秒部分,下表是MySQL各时间类型的格式样例
Data Type | “Zero” Value |
DATE | '0000-00-00' |
TIME | '00:00:00' |
DATETIME | '0000-00-00 00:00:00' |
TIMESTAMP | '0000-00-00 00:00:00' |
YEAR | 0000 |
DATETIME与TIMESTAMP类型的区别
DATETIME | TIMESTAMP | |
存储长度 | 8字节 | 4字节 |
时区支持 | 不支持 | 支持 |
表达范围 | 1000-01-01 00:00:00 9999-12-31 23:59:59 | 1970-01-01 00:00:01 2038-01-19 03:14:07 |
保存格式 | 实际格式保存 | UTC格式 |
规则4:ORACLE转MySQL之NUMBER字段类型转换;
number(M,N)如果N是0则为整形,对应MySQL的整形类型,下面是MySQL的各整形类型的所需字节数及能表达的范围(摘抄至官网5.6),
Type | Storage | Minimum Value | Maximum Value |
| (Bytes) | (Signed/Unsigned) | (Signed/Unsigned) |
TINYINT | 1 | -128 | 127 |
|
| 0 | 255 |
SMALLINT | 2 | -32768 | 32767 |
|
| 0 | 65535 |
MEDIUMINT | 3 | -8388608 | 8388607 |
|
| 0 | 16777215 |
INT | 4 | -2147483648 | 2147483647 |
|
| 0 | 4294967295 |
BIGINT | 8 | -9223372036854775808 | 9223372036854775807 |
|
| 0 | 18446744073709551615 |
如果number(M,N)中N大于0则对应MySQL的decimal类型,如oracle的number(5,2)则MySQL为decimal(5,2),值得注意的是,在超出范围的情况下ORACLE会报错,而MySQL会取它能表示的最大值来替代原来的值,如decimal(5,2)/number(5,2)能表达的范围为 -999.99-999.99 如果要插入1000的数据,oracle会提示超表达范围的错,而MySQL会以999.99来替代
规则5:固定长度的字符串使用CHAR,单字符字段使用CHAR(1)类型;
规则6:字段避免使用NULL值,用默认值来替代,修改时间,审核时间等用“0000-00-00 00:00:00”这样的默认值进行替代,备注用‘’的空字符进替代;
规则7:不建议使用ENUM,SET类型,用TINYINT替代;
建议1: 尽量不使用BLOB,TEXT类型,大字段建议单独设计表,通过关联进行查找;
建议2: 建议使用UNSIGNED 存储非负数值;
同样的字节数,存储的数值范围更大
4.3.3 索引
规则1: 无特别说明,每个表的索引不得超过5个;
规则2: 单字段上的索引不得超过2个;(即一个单字段最多可在上面建立一个单字段索引和一个组合索引包含这个字段)
规则3: 复合索引原则上不得超过3个字段;
规则4: 外键列需要创建索引;
建议1: 频繁出现在where子句里的字段建议建立索引;
建议2: 用来和其他表关联的字段建议建立索引;
建议3: 索引字段建议有高的选择性和过滤性(count(distinct)/count(*)>0.6);
建议4: 建立索引的时候,建议考虑到SELECT和INSERT,UPDATE,DELETE的平衡;
建议5: 一般建议在查询数据量10%以下使用索引;
建议6: WHERE子句的查询条件构成索引字段前导字段;
建议7: 选择性更高的字段放在组合字段索引的前导字段;
建议8: 如果字段选择性接近,则把频繁查询的字段放在前面;
建议9: 进行GROUP BY或者是ORDER BY的字段应在组合字段索引的前导字段;
4.3.6 视图
规则1:视图中不允许出现ORDER BY排序;
规则2:基于多表关联的视图,必须在字段名前指定表别名;
建议1:视图的基础数据尽量从表中获取,尽量不要嵌套视图;
4.3.7 存储过程
规则1: 避免将业务逻辑放在存储过程中,那样容易将业务逻辑和DB耦合在一起;
规则2: 存储过程,必须有异常捕获代码;
规则3: 存储过程中严禁使用GOTO语句进行跳转;
规则4: 有循环更新的存储过程,必须进行批量提交,且必须进行事务控制;
说明:MySQL存储过程中必须用START TRANSACTION 来显示开始一个事务,否则会按默认的每个DML做个一个事务。
规则5: 存储过程中如果使用了游标,则在存储过程正常或者异常退出必须关闭所有打开的游标;
规则6: 存储过程中如果有更新,必须在异常捕获代码中做回退操作;
规则7: 注释格式如下,存储过程说明放在在COMMENT中,其他用”##”进行注释说明;
CREATE PROCEDURE prc_vendor
COMMENT '说明:同步下发接收; 参数:xxxx; 返回:标志0=成功;’
##建立:xxx 2012.07.17
## Modify by xxx 2012.08.08 增加供应商状态字段
##Modify by xxx 2012.11.10 增加供应商英文名称字段
BEGIN
...
END;
建议1:存储过程每次被更新,应在注释中说明更新的内容,更新的日期,以及更新的责任人,并且在更新前保留旧版本代码
建议2:尽量避免在存储过程中使用动态SQL。
4.3.8 函数
规则1: 避免将业务逻辑放在函数中,那样容易将业务逻辑和DB耦合在一起;
规则2: 函数中,如果进行了事务处理,必须有异常捕获代码;
规则3: 函数中严禁使用GOTO语句进行跳转;
规则4: 有循环更新的函数,必须进行批量提交,且必须进行事务控制;
规则5: 函数中如果使用了游标,则在函数正常或者异常退出必须关闭所有打开的游标;
规则6: 函数中如果对数据进行了更新操作,必须在异常捕获代码中做回退操作;
规则7: 注释格式如下
CREATE FUNCTION func_get_serialno
(
p_request VARCHAR ###请求编号
)
return VARCHAR
COMMENT ’说明:产生序列号函数,通过serialno_config配置表响应产生序列号; 参数:p_requestid 请求编号 返回:返回需要的序列号‘
##建立:xxx 2013.07.02
##modified by xxx 2014-6-18 xxx
BEGIN
...
END;
建议1:函数每次被更新,应在注释中说明更新的内容,更新的日期,以及更新的责任人,并且在更新前保留旧版本代码;
建议2:函数尽量只是实现复杂的计算功能,不对数据库进行更新操作;
4.3.9 触发器
规范1:如无必要,不得设计触发器,任何触发器的设计,必须得到DBA批准;
规范2:应用的完整性不应由触发器保证,而是通过代码的事务控制;
建议1:有高度一致性依赖的逻辑,触发器应设计为BEFORE而非AFTER方式;
4.4 开发规范
4.4.1. 基本规范
规则1: 避免使用存储过程、触发器、函数等,容易将业务逻辑和DB耦合在一起,并且MySQL的存储过程、触发器、函数中存在一定的BUG;
规则2: 禁止进行字段数据类型的隐式转换,所有转换必须进行明确的数据类型转换;
说明:隐式转换会导致字段上的索引失效, 最为常见的隐式类型转换常见于时间类型与字符串类型之间,建议所有时间类型字段在myBatis中均以时间类型传入,或者以字符串传入然后通过时间函数转换字符串为合法的时间格式 ,如下:
SELECT name
FROM member
WHERE vgmt_create=DATE_FORMATE('2009010101:02:03','%Y-%m-%d %H:%i:%s');
规则3: 禁止在多表关联的时候,在非索引字段上的关联;
规则4: 进行模糊查询时,禁止条件中字符串直接以“%”开头;
规则5: 在使用for update子句时一定注意限制条件,避免锁定全表或者不需要被锁定的行记录。如无必要锁定数据则应避免使用for update;
规则6: 在进行结果集合并(union或union all)时, 如不需要进行结果去重,则必须使用union all,而不能使用union;且尽量减少进行数据集的去重;
规则7: 除非必要,避免使用 != 等非等值操作符,会导致用不到索引;
规则8: 禁止在 WHERE 条件中出现的过滤字段上使用任何函数进行类型或格式的转换;正确的做法是把传入的值转换为列类型所需要的;
错误的写法:
SELECT username
FROM gl_user
WHERE DATE_FORMAT(gmt_create, %Y%m%d%H%i%s')='20090501022300‘;
正确的写法:
SELECT username
FROM gl_user
WHERE gmt_create=DATE_FORMAT('20090501022300', '%Y-%m-%d %H:%i:s');
规则9: 禁止使用order by rand();
order by rand() 会将数据从磁盘中读取,进行排序,会消耗大量的IO和CPU。
规则10:避免大使用大SQL,可以拆分成多个小SQL来替代;
4.4.2. 绑定变量使用规范
规则1: 应用端所有查询的 where 条件中的变量,都需要使用绑定变量来实现,以防SQL注入,同时性能也会更优;
规则2: 在 myBatis 的 SqlMap 文件中绑定变量使用 "#{var_name}"表示,替代变量使用"${var_name}";所有需要动态 Order By 条件的查询,在使用替代变量过程中,需要将可能传入的内容以枚举类写死在代码中,禁止接受任何外部传入内容;对于不变的常量条件,请使用常量而不是变量;
规则3: IN子句,使用"Iterate + 数组类型变量"的方式实现绑定变量而不是通过代码拼接 Query 语句;
例如:
myBatis会生成user_level in (1,2,3,4,5 ...)的语句
4.4.3. 分页规范
假如有类似下面分页语句:
SELECT * FROM table ORDER BY create_time DESC LIMIT 10000,10;
这种分页方式会导致大量的IO,因为MySQL使用的是提前读取策略。
推荐分页方式:
SELECT * FROM table WEHRE create_time < last_time ORDER BY create_time DESC LIMIT 10;
SELECT * FROM table INNER JOIN (SELECT id FROM table ORDER BY create_time LIMIT 10000,10) AS t USING(id);
4.4.4. 建议
建议1: 尽量使用if来简化SQL访问数据库的次数;
示例:
如下两个语句实现的功能
SELECT COUNT(*) , SUM(salary)
FROM employees
WHERE department_id = 20
AND first_name LIKE 'SMITH%';
SELECT COUNT(*) , SUM(salary)
FROM employees
WHERE department_id = 30
AND first_name LIKE 'SMITH%';
可以使用IF改写为如下语句
SELECT COUNT(IF(department_id=20, '*', NULL)) d20_count,
COUNT(IF(department_id=30, '*', NULL)) d30_count,
SUM(IF(department_id=20, salary, NULL)) d20_sal,
SUM(IF(department_id=30, salary, NULL)) d30_sal
FROM employees
WHERE first_name LIKE 'SMITH%';
建议2: 避免使用HAVING子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作. 如果能通过WHERE子句限制记录的数目,那就能减少这方面的开销;
示例
如下语句使用的是HAVING子句
SELECT last_name, avg(salary)
FROM employees
GROUP BY last_name
HAVING last_name != 'Grant'
AND last_name != 'Fay'
改写使用WHERE的语句如下
SELECT last_name, avg(salary)
FROM employees
WHERE last_name != 'Grant'
AND last_name != 'Fay'
GROUP BY last_name
建议3: 尽量少用not exist/no in等反向写法。如果一定要用时,尽量选择not exist;
建议4: 尽量少用is null/is not null等null的处理;
建议5: SQL语句中IN子句里的值不应超过300;
建议6: 对于大表查询中的列项应尽量避免进行诸如CAST()或CONVERT()的转换;
建议7: 尽量避免进行全表扫描,限制条件尽可能多,以便更快搜索到要查询的数据;
建议8: SELECT查询语句建议增加limit 1000 限定返回的行数;
建议9: 进行数据库结构设计的时候,考虑适当的冗余,尽量确保应用读写数据的SQL简洁;
建议10: 要返回MySQL自增序列的ID值,可以考虑使用函数LAST_INSERT_ID(),此函数只能返回同
一个SESSION最近一次对有AUTO_INCREMENT属性表INSERT的ID值
4.5. 开发实用技术
4.5.1. CHAR(N)或VARCHAR(N)中的N解释
MySQL中此两类字符串定义时候填写的长度N,不是字节数的意思 ,而是字符数的意思。
我们MySQL所有数据库的字符集都为UTF8,字符集校对规则为UTF8_bin。对于中文汉字,实际存储的时候占三个字节,而数据或字母,则只占一个字节。例如:
CREATE TABEL company_inventory (color VARCHAR(44) COMMENT '颜色');
则color最多能存储40个字符。
4.5.2. 日期操作函数
获取当前时间:NOW(),CURDATE()、CURTIME()
其中, NOW()函数精确到秒, 格式:YYYY-MM-DD HH:MM:SS
CURDATE函数精确到天,格式:YYYY-MM-DD
CURTIME函数精确到秒,格式:HH:MM:SS
日期数值的加减函数:
DATE_ADD(date,INTERVAL expr type)
DATE_ SUB(date,INTERVAL expr type)
常用的几种type类型:YEAR、MONTH、DAY、HOUR、MINUTE,其中expr可以为正数或负数,我们在开过程中,一般使用DATE_ADD()函数,若要作日期减去一个数字的方式,就使用负数。
DATEDIFF(expr1,expr2),是返回 开始日期expr1与 结束日期expr2之间,相差的天数 ,返回值为正数或负数。
返回日期某部分信息的函数:
YEAR(expr1) 返回日期expr1部分的年份;
MONTH(expr1) 返回日期expr1部分的月份;
DAY(expr1)返回expr1部分的天数;
WEEKDAY(expr1)返回expr1对应的星期数字
4.5.3. 类型转换函数
字符串转换成日期方式,DATE_FORMAT()或STR_TO_DATE(),
两个函数的格式如下:
DATE_FORMAT(expr1,format)
STR_TO_DATE(expr1, format)
常用的日期格式YYYY-MM-DD HH:MM:SS 对应的format为%Y-%m-%d %H:%i:%S
通用的类型转换函数:
CAST(expr AS type)
CONVERT(expr,type)
CONVERT(expr USING transcoding_name)
4.5.4. INNODB与MYISAM的主要区别
MyISAM | InnoDB | |
构 成上的区别: | 每个MyISAM表在磁盘上存储三个文件。文件的名字以表的名字开始,扩展名指出文件类型。 .frm文件存储表定义。 .MYD文件存储数据 (MYData)。 .MYI文件存储索引 (MYIndex)。 | InnoDB表空间数据文件和日志文件,InnoDB表的大小只受限于操作系统文件的大小 |
事务处理上方面: | MyISAM类型的表强调的是性能,其执行速度比InnoDB类型更快,但是不提供事务支持 | InnoDB提供事务支持事务,外部键等高级 数据库功能 |
SELECT UPDATE,INSERT,Delete操 作 | 如果执行大量的SELECT,MyISAM是更好的选择 | 1.如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表 2.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的 删除。 3.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用 |
表的具体行数 | select count(*) from table,MyISAM只要简单的读出保存好的行数,注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的 | InnoDB中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行 |
锁 | 表锁,MyISAM表锁读写互相阻塞,写锁优先级高于读锁。参数选项low_priority_updates设置写锁优先级比读锁低、参数选项concurrent_insert配置是否使用并发插入特性,concurrent_insert=0 表示不允许并发插入,concurrent_insert=1表示允许对没有空数据块的表使用并发插入(缺省),concurrent_insert=2表示对所有表允许并发插入 | 提供行锁(locking on row level),提供与Oracle类型一致的不加锁读取(non-locking read in SELECTs)。MySQL的行锁是针对索引加的锁,不是针对记录加的锁,在不通过索引条件查询的时候,InnoDB使用的是表锁,而不是行锁。 另外,即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁 |
版权声明:本文为博主原创文章,未经博主允许不得转载。