外观
MySQL SQL 编写规范
命名规范
1. 数据库命名
- 命名规则:使用小写字母、数字和下划线组合,避免使用特殊字符
- 命名长度:建议不超过 32 个字符,符合 MySQL 标识符长度限制
- 命名格式:采用业务名称或项目名称,如
ecommerce,blog_platform - 示例:sql
-- 推荐 CREATE DATABASE ecommerce; CREATE DATABASE blog_platform; -- 不推荐 CREATE DATABASE ECommerce; -- 大小写混合 CREATE DATABASE 电商平台; -- 中文名称 CREATE DATABASE my-db; -- 特殊字符
2. 表命名
- 命名规则:使用小写字母、数字和下划线组合,避免使用特殊字符
- 命名长度:建议不超过 32 个字符
- 命名格式:采用
业务模块_表名或表名格式,如user_info,order_detail - 避免使用保留字:如
order,table,select等 - 示例:sql
-- 推荐 CREATE TABLE user_info ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL ); CREATE TABLE order_detail ( id INT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL ); -- 不推荐 CREATE TABLE UserInfo; -- 驼峰命名 CREATE TABLE order; -- 保留字 CREATE TABLE 订单详情; -- 中文名称
3. 字段命名
- 命名规则:使用小写字母、数字和下划线组合,避免使用特殊字符
- 命名长度:建议不超过 32 个字符
- 命名格式:采用
字段含义或业务_字段含义格式,如user_id,product_name - 避免使用缩写:除非是广为人知的缩写,如
id,url,ip - 示例:sql
-- 推荐 CREATE TABLE user ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ); -- 不推荐 CREATE TABLE user ( uid INT PRIMARY KEY, -- 不明确的缩写 uname VARCHAR(50) NOT NULL, -- 不明确的缩写 email VARCHAR(100) NOT NULL, ctime DATETIME NOT NULL, -- 不明确的缩写 utime DATETIME NOT NULL -- 不明确的缩写 );
4. 索引命名
- 命名规则:使用小写字母、数字和下划线组合
- 命名格式:
- 主键索引:
PRIMARY KEY(MySQL 自动命名为PRIMARY) - 唯一索引:
uk_表名_字段名,如uk_user_username - 普通索引:
idx_表名_字段名,如idx_user_email - 复合索引:
idx_表名_字段1_字段2,如idx_order_userid_createdat
- 主键索引:
- 示例:sql
-- 推荐 CREATE TABLE user ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY uk_user_username (username), INDEX idx_user_email (email), INDEX idx_user_createdat (created_at) ); CREATE TABLE order ( id INT PRIMARY KEY, user_id INT NOT NULL, created_at DATETIME NOT NULL, status VARCHAR(20) NOT NULL, INDEX idx_order_userid_createdat (user_id, created_at) );
5. 存储过程和函数命名
- 命名规则:使用小写字母、数字和下划线组合
- 命名格式:
- 存储过程:
proc_功能描述,如proc_user_register - 函数:
func_功能描述,如func_calculate_total
- 存储过程:
- 示例:sql
-- 推荐 DELIMITER // CREATE PROCEDURE proc_user_register( IN p_username VARCHAR(50), IN p_email VARCHAR(100), OUT p_user_id INT )
BEGIN -- 存储过程逻辑 END // DELIMITER ;
CREATE FUNCTION func_calculate_total( p_quantity INT, p_price DECIMAL(10,2) ) RETURNS DECIMAL(10,2) BEGIN RETURN p_quantity * p_price; END;
## 格式规范
### 1. 缩进和换行
- **缩进**:使用 4 个空格进行缩进,避免使用制表符
- **换行**:
- SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY 等关键字单独占一行
- 多个字段或条件时,每个字段或条件单独占一行
- JOIN 子句单独占一行,ON 条件单独占一行
- **示例**:
```sql
-- 推荐
SELECT
u.id,
u.username,
u.email,
o.id AS order_id,
o.total_amount,
o.created_at
FROM
user u
JOIN
order o ON u.id = o.user_id
WHERE
u.created_at >= '2023-01-01'
AND o.status = 'completed'
ORDER BY
o.created_at DESC;
-- 不推荐
SELECT u.id, u.username, u.email, o.id AS order_id, o.total_amount, o.created_at FROM user u JOIN order o ON u.id = o.user_id WHERE u.created_at >= '2023-01-01' AND o.status = 'completed' ORDER BY o.created_at DESC;2. 大小写规范
- 关键字:SQL 关键字使用大写,如 SELECT、FROM、WHERE、JOIN 等
- 标识符:数据库名、表名、字段名、索引名等使用小写
- 函数名:内置函数名使用大写,如 COUNT(), SUM(), DATE() 等
- 示例:sql
-- 推荐 SELECT COUNT(*) AS total_users, MAX(created_at) AS latest_user FROM user WHERE status = 'active'; -- 不推荐 select count(*) as total_users, max(created_at) as latest_user from User where Status = 'active';
3. 空格使用
- 操作符两侧:在
=,+,-,*,/,<>,>=,<=等操作符两侧添加空格 - 逗号后:在逗号后添加空格
- 括号内:括号内不添加空格,如
COUNT(*)而不是COUNT( *) - 示例:sql
-- 推荐 SELECT id, username, email FROM user WHERE age >= 18 AND status = 'active' ORDER BY created_at DESC; -- 不推荐 SELECT id,username,email FROM user WHERE age>=18 AND status='active' ORDER BY created_at DESC;
4. 注释规范
- 单行注释:使用
--(双横线加空格)进行单行注释 - 多行注释:使用
/* */进行多行注释 - 注释位置:
- 表和字段创建时添加注释
- 复杂 SQL 语句添加注释
- 存储过程和函数添加注释
- 示例:sql
-- 推荐 /* * 用户信息表 * 存储系统用户的基本信息 */ CREATE TABLE user_info ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL COMMENT '用户名', email VARCHAR(100) NOT NULL COMMENT '邮箱地址', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' ) COMMENT='用户信息表'; -- 查询 2023 年注册的活跃用户 SELECT id, username, email FROM user_info WHERE YEAR(created_at) = 2023 -- 2023 年注册 AND status = 'active'; -- 活跃用户
性能优化规范
1. 查询优化
避免 SELECT *
- 问题:
SELECT *会查询所有字段,增加网络传输开销,可能无法使用覆盖索引 - 解决方案:明确指定需要查询的字段
- 示例:sql
-- 推荐 SELECT id, username, email FROM user; -- 不推荐 SELECT * FROM user;
使用 LIMIT 限制返回行数
- 问题:不使用 LIMIT 可能返回大量数据,影响性能
- 解决方案:使用 LIMIT 限制返回行数,特别是在分页查询中
- 示例:sql
-- 推荐 SELECT id, username, email FROM user ORDER BY created_at DESC LIMIT 10; -- 分页查询 SELECT id, username, email FROM user ORDER BY created_at DESC LIMIT 10 OFFSET 20;
合理使用索引
- 问题:缺少索引会导致全表扫描,影响查询性能
- 解决方案:为经常查询的字段创建索引
- 注意事项:
- 不要过度索引,索引会影响写入性能
- 考虑联合索引的顺序,将选择性高的字段放在前面
- 避免在索引字段上使用函数或表达式
- 示例:sql
-- 推荐 SELECT id, username, email FROM user WHERE email = 'user@example.com'; -- email 字段有索引 -- 不推荐 SELECT id, username, email FROM user WHERE SUBSTRING(email, 1, 5) = 'user@'; -- 在索引字段上使用函数
避免在 WHERE 子句中使用函数或表达式
- 问题:在 WHERE 子句中使用函数或表达式会导致索引失效
- 解决方案:将函数或表达式移到等号右侧,或使用计算列
- 示例:sql
-- 推荐 SELECT id, username, email FROM user WHERE created_at >= '2023-01-01'; -- 不推荐 SELECT id, username, email FROM user WHERE DATE(created_at) = '2023-01-01'; -- 在索引字段上使用函数
2. 写入优化
使用批量插入
- 问题:单条插入语句效率低下,特别是大量数据插入时
- 解决方案:使用批量插入,减少网络往返次数
- 示例:sql
-- 推荐 INSERT INTO user (username, email) VALUES ('user1', 'user1@example.com'), ('user2', 'user2@example.com'), ('user3', 'user3@example.com'); -- 不推荐 INSERT INTO user (username, email) VALUES ('user1', 'user1@example.com'); INSERT INTO user (username, email) VALUES ('user2', 'user2@example.com'); INSERT INTO user (username, email) VALUES ('user3', 'user3@example.com');
避免使用 INSERT SELECT *
- 问题:
INSERT SELECT *可能插入不需要的字段,影响性能和数据完整性 - 解决方案:明确指定需要插入的字段
- 示例:sql
-- 推荐 INSERT INTO user_archive (id, username, email, created_at) SELECT id, username, email, created_at FROM user WHERE status = 'inactive'; -- 不推荐 INSERT INTO user_archive SELECT * FROM user WHERE status = 'inactive';
合理使用事务
- 问题:长事务会占用锁资源,影响并发性能
- 解决方案:
- 缩短事务长度,尽快提交或回滚
- 避免在事务中执行耗时操作
- 合理设置事务隔离级别
- 示例:sql
-- 推荐 START TRANSACTION; UPDATE order SET status = 'paid' WHERE id = 1; INSERT INTO payment (order_id, amount, payment_method) VALUES (1, 100.00, 'credit_card'); COMMIT; -- 不推荐 START TRANSACTION; UPDATE order SET status = 'paid' WHERE id = 1; -- 执行耗时操作 DO SLEEP(10); INSERT INTO payment (order_id, amount, payment_method) VALUES (1, 100.00, 'credit_card'); COMMIT;
3. 表设计优化
选择合适的数据类型
- 问题:使用不合适的数据类型会浪费存储空间,影响性能
- 解决方案:
- 整数类型:根据范围选择 TINYINT, SMALLINT, INT, BIGINT
- 字符串类型:根据长度选择 CHAR, VARCHAR, TEXT
- 日期时间类型:根据精度选择 DATE, DATETIME, TIMESTAMP
- 示例:sql
-- 推荐 CREATE TABLE user ( id INT PRIMARY KEY, -- 适用于中小型用户量 age TINYINT NOT NULL, -- 年龄范围 0-255 username VARCHAR(50) NOT NULL, -- 可变长度字符串 created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 时间戳,自动更新 bio TEXT -- 长文本 ); -- 不推荐 CREATE TABLE user ( id BIGINT PRIMARY KEY, -- 不必要的大类型 age INT NOT NULL, -- 年龄使用 INT 浪费空间 username CHAR(50) NOT NULL, -- 固定长度字符串浪费空间 created_at DATETIME NOT NULL, -- 没有默认值 bio VARCHAR(65535) -- 应该使用 TEXT 类型 );
避免使用 NULL
- 问题:NULL 值会增加查询复杂性,影响索引性能
- 解决方案:
- 为字段设置默认值
- 使用 NOT NULL 约束
- 对于字符串类型,使用空字符串 '' 代替 NULL
- 示例:sql
-- 推荐 CREATE TABLE user ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', -- 默认值 bio TEXT NOT NULL DEFAULT '' -- 空字符串代替 NULL ); -- 不推荐 CREATE TABLE user ( id INT PRIMARY KEY, username VARCHAR(50) NULL, email VARCHAR(100) NULL, status VARCHAR(20) NULL, bio TEXT NULL );
合理使用范式
- 问题:过度范式化会导致复杂的 JOIN 查询,影响性能;过度反范式化会导致数据冗余
- 解决方案:
- 主要表遵循第三范式
- 适当反范式化,如在订单表中存储用户名,避免 JOIN 查询
- 使用缓存或物化视图处理复杂查询
安全规范
1. 避免 SQL 注入
- 问题:SQL 注入是最常见的数据库安全漏洞
- 解决方案:
- 使用参数化查询或预处理语句
- 避免拼接 SQL 语句
- 对输入进行验证和过滤
- 示例:sql
-- 推荐:使用参数化查询 PREPARE stmt FROM 'SELECT * FROM user WHERE username = ?'; SET @username = 'user1'; EXECUTE stmt USING @username; DEALLOCATE PREPARE stmt; -- 不推荐:直接拼接 SQL SET @username = 'user1'; SET @sql = CONCAT('SELECT * FROM user WHERE username = ''', @username, ''''); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
2. 最小权限原则
- 问题:给予过多权限会增加安全风险
- 解决方案:
- 为用户授予最小必要权限
- 避免使用 root 用户进行日常操作
- 定期审查用户权限
- 示例:sql
-- 推荐 CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT, INSERT, UPDATE, DELETE ON ecommerce.* TO 'app_user'@'localhost'; -- 不推荐 GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%' IDENTIFIED BY 'password';
3. 加密敏感数据
- 问题:敏感数据明文存储会增加数据泄露风险
- 解决方案:
- 使用 MySQL 加密函数加密敏感数据
- 使用 SSL/TLS 加密数据传输
- 考虑使用透明数据加密 (TDE)
- 示例:sql
-- 推荐:使用 AES 加密 INSERT INTO user (username, password, email) VALUES ('user1', AES_ENCRYPT('password123', 'encryption_key'), 'user1@example.com'); SELECT username, AES_DECRYPT(password, 'encryption_key') AS password FROM user WHERE username = 'user1';
4. 避免使用 SELECT * 查询敏感数据
- 问题:SELECT * 可能返回敏感数据,增加数据泄露风险
- 解决方案:明确指定需要查询的字段,避免返回敏感数据
- 示例:sql
-- 推荐 SELECT id, username, email FROM user WHERE id = 1; -- 不推荐 SELECT * FROM user WHERE id = 1; -- 可能返回密码等敏感数据
事务处理规范
1. 明确指定事务隔离级别
- 问题:不同的事务隔离级别会影响并发性能和数据一致性
- 解决方案:根据业务需求选择合适的隔离级别
- 隔离级别:
- READ UNCOMMITTED:读取未提交的数据
- READ COMMITTED:读取已提交的数据
- REPEATABLE READ:可重复读(MySQL 默认)
- SERIALIZABLE:串行化
- 示例:sql
-- 推荐:明确指定隔离级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; -- 事务操作 COMMIT;
2. 及时提交或回滚事务
- 问题:长事务会占用锁资源,影响并发性能
- 解决方案:
- 尽快提交或回滚事务
- 避免在事务中执行耗时操作
- 设置合理的事务超时时间
- 示例:sql
-- 推荐 START TRANSACTION; -- 执行事务操作 UPDATE order SET status = 'paid' WHERE id = 1; INSERT INTO payment (order_id, amount) VALUES (1, 100.00); -- 及时提交 COMMIT; -- 不推荐 START TRANSACTION; -- 执行事务操作 UPDATE order SET status = 'paid' WHERE id = 1; -- 执行耗时操作 SELECT SLEEP(10); INSERT INTO payment (order_id, amount) VALUES (1, 100.00); COMMIT;
3. 处理事务异常
- 问题:事务异常可能导致数据不一致
- 解决方案:
- 使用 TRY-CATCH 块处理异常(MySQL 8.0+)
- 在应用程序中处理事务异常
- 定期检查和修复数据一致性
- 示例:sql
-- MySQL 8.0+:使用 TRY-CATCH START TRANSACTION; BEGIN UPDATE order SET status = 'paid' WHERE id = 1; INSERT INTO payment (order_id, amount) VALUES (1, 100.00); COMMIT; EXCEPTION ROLLBACK; -- 处理异常 END;
存储过程和函数规范
1. 命名和格式
- 命名:使用小写字母、数字和下划线组合,如
proc_user_register,func_calculate_total - 格式:
- 输入参数以
p_开头,如p_username - 输出参数以
o_开头,如o_user_id - 局部变量以
v_开头,如v_total
- 输入参数以
- 示例:sql
DELIMITER // CREATE PROCEDURE proc_user_register( IN p_username VARCHAR(50), IN p_email VARCHAR(100), OUT o_user_id INT )
BEGIN DECLARE v_existing_count INT;
-- 检查用户名是否已存在
SELECT COUNT(*) INTO v_existing_count FROM user WHERE username = p_username;
IF v_existing_count = 0 THEN
INSERT INTO user (username, email) VALUES (p_username, p_email);
SET o_user_id = LAST_INSERT_ID();
ELSE
SET o_user_id = NULL;
END IF;
END // DELIMITER ;
### 2. 错误处理
- **问题**:缺少错误处理会导致存储过程或函数异常终止
- **解决方案**:
- 使用 DECLARE HANDLER 处理异常
- 设置合理的错误码和错误信息
- 记录错误日志
- **示例**:
```sql
DELIMITER //
CREATE PROCEDURE proc_deposit(
IN p_account_id INT,
IN p_amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '存款失败';
END;
START TRANSACTION;
UPDATE account SET balance = balance + p_amount WHERE id = p_account_id;
INSERT INTO transaction (account_id, type, amount) VALUES (p_account_id, 'deposit', p_amount);
COMMIT;
END //
DELIMITER ;3. 性能优化
- 问题:存储过程和函数可能导致性能问题
- 解决方案:
- 避免在存储过程中使用游标,使用集合操作代替
- 合理使用索引
- 避免在存储过程中执行大量数据操作
- 定期优化和重构存储过程
常见问题(FAQ)
Q1: 为什么要遵循 SQL 编写规范?
A1: 遵循 SQL 编写规范的好处:
- 提高可读性:统一的格式和命名使 SQL 语句更容易阅读和理解
- 提高可维护性:规范的代码便于团队协作和后续维护
- 提高性能:合理的 SQL 编写可以提高查询和写入性能
- 提高安全性:遵循安全规范可以减少 SQL 注入等安全漏洞
- 符合最佳实践:遵循行业最佳实践,避免常见错误
Q2: 如何选择合适的数据类型?
A2: 选择数据类型时需要考虑以下因素:
- 数据范围:根据数据的实际范围选择合适的类型
- 存储空间:选择占用空间最小的类型
- 查询性能:考虑索引性能和查询效率
- 数据完整性:选择能够确保数据完整性的类型
- 兼容性:考虑与应用程序的兼容性
Q3: 如何避免 SQL 注入?
A3: 避免 SQL 注入的方法:
- 使用参数化查询或预处理语句
- 避免拼接 SQL 语句
- 对输入进行验证和过滤
- 限制用户权限,遵循最小权限原则
- 使用存储过程处理复杂逻辑
Q4: 如何优化查询性能?
A4: 优化查询性能的方法:
- 为经常查询的字段创建索引
- 避免使用 SELECT *,明确指定需要的字段
- 避免在 WHERE 子句中使用函数或表达式
- 使用 LIMIT 限制返回行数
- 合理使用 JOIN 查询,避免过多 JOIN
- 考虑使用缓存或物化视图
Q5: 如何处理长事务?
A5: 处理长事务的方法:
- 缩短事务长度,尽快提交或回滚
- 避免在事务中执行耗时操作
- 合理设置事务隔离级别
- 使用乐观锁代替悲观锁
- 考虑将长事务拆分为多个短事务
Q6: 如何选择合适的索引策略?
A6: 选择索引策略时需要考虑以下因素:
- 查询模式:根据常见的查询条件创建索引
- 数据分布:考虑字段的数据分布,选择选择性高的字段
- 写入性能:索引会影响写入性能,避免过度索引
- 联合索引顺序:将选择性高的字段放在前面
- 覆盖索引:考虑创建覆盖索引,避免回表查询
Q7: 如何处理 NULL 值?
A7: 处理 NULL 值的方法:
- 为字段设置默认值
- 使用 NOT NULL 约束
- 对于字符串类型,使用空字符串 '' 代替 NULL
- 在查询中使用 IS NULL 或 IS NOT NULL 进行判断
- 考虑使用 COALESCE() 函数处理 NULL 值
Q8: 如何确保数据安全性?
A8: 确保数据安全性的方法:
- 使用参数化查询避免 SQL 注入
- 遵循最小权限原则,限制用户权限
- 加密敏感数据
- 使用 SSL/TLS 加密数据传输
- 定期备份数据
- 实施访问控制和审计
案例分析
案例1:优化慢查询
场景描述
某电商平台的订单查询页面响应时间较长,经分析发现是以下 SQL 查询导致的:
sql
SELECT * FROM order WHERE user_id = 123 AND created_at >= '2023-01-01' ORDER BY id DESC;问题分析
- 使用了 SELECT *,返回不必要的字段
- order 表有 1000 万条记录,没有合适的索引
- 查询条件包含 user_id 和 created_at,但只有 user_id 上有索引
解决方案
- 明确指定需要的字段,避免使用 SELECT *
- 创建联合索引 idx_order_userid_createdat (user_id, created_at)
- 优化后的 SQL:
sql
SELECT id, order_no, total_amount, status, created_at
FROM order
WHERE user_id = 123 AND created_at >= '2023-01-01'
ORDER BY id DESC;优化效果
- 查询响应时间从 5 秒降低到 0.1 秒
- 扫描的行数从 100 万减少到 100
- 索引利用率从 20% 提高到 100%
案例2:避免 SQL 注入
场景描述
某应用程序使用以下方式执行 SQL 查询:
java
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM user WHERE username = '" + username + "' AND password = '" + password + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);问题分析
- 直接拼接 SQL 语句,存在 SQL 注入风险
- 攻击者可以通过输入
' OR '1'='1绕过身份验证
解决方案
使用预处理语句:
java
String username = request.getParameter("username");
String password = request.getParameter("password");
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();优化效果
- 消除了 SQL 注入风险
- 提高了查询性能,预处理语句可以被缓存
- 代码更安全,更易于维护
