1. SQL语言四大分类概述
SQL(Structured Query Language)作为关系型数据库的标准查询语言,根据功能不同可分为四大类:数据定义语言(DDL)、数据操纵语言(DML)、数据查询语言(DQL)和数据控制语言(DCL)。这四类语言各司其职,共同构成了完整的数据库操作体系。
在实际开发中,我经常遇到新手开发者混淆这些分类的情况。比如有人用DDL语句去修改数据记录,或者试图用DML语句创建表结构。理解这些分类的本质区别,是写出高效、安全SQL语句的基础。
提示:虽然不同数据库系统(MySQL、Oracle、SQL Server等)在语法细节上略有差异,但这四大分类是所有关系型数据库通用的标准。
2. 数据定义语言(DDL)详解
2.1 DDL的核心功能与语法
DDL用于定义和管理数据库对象的结构,主要操作对象是数据库和表。其核心命令包括:
- CREATE:创建数据库或表
- ALTER:修改表结构
- DROP:删除数据库或表
- TRUNCATE:清空表数据(保留表结构)
创建数据库的典型语法:
CREATE DATABASE school CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这里我特别指定了字符集和排序规则,这是实际项目中经常被忽视但极其重要的细节。utf8mb4支持完整的Unicode字符(包括emoji),而utf8在MySQL中最多只支持3字节的UTF-8字符。
2.2 表操作实战技巧
创建表示例:
CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, email VARCHAR(100) UNIQUE, admission_date DATE DEFAULT CURRENT_DATE, INDEX idx_name (name) ) ENGINE=InnoDB;几个关键点:
- 使用AUTO_INCREMENT实现自增主键
- NOT NULL约束确保数据完整性
- UNIQUE约束防止重复邮箱
- 为name字段创建索引提高查询效率
- 显式指定InnoDB引擎(支持事务)
注意:VARCHAR长度应根据实际业务需求合理设置,过大会浪费存储空间,过小会导致数据截断。我曾遇到一个项目因为email字段设为VARCHAR(50)导致大量用户无法注册。
2.3 修改表结构的注意事项
ALTER TABLE可能是最危险的DDL操作之一。在大表上执行ALTER可能导致长时间锁表,影响线上服务。分享几个实战经验:
- 添加列时指定DEFAULT值:
ALTER TABLE student ADD COLUMN gender CHAR(1) DEFAULT 'U' COMMENT 'U:未知, M:男, F:女';- 修改列类型要谨慎:
-- 可能导致数据丢失 ALTER TABLE student MODIFY COLUMN name CHAR(50); -- 更安全的做法是先备份 CREATE TABLE student_backup AS SELECT * FROM student;- 删除列前确认依赖关系:
-- 检查是否有外键约束 SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'school' AND TABLE_NAME = 'student';3. 数据操纵语言(DML)深度解析
3.1 DML基础操作
DML用于操作表中的记录,主要包括:
- INSERT:插入新记录
- UPDATE:更新现有记录
- DELETE:删除记录
插入数据的几种方式:
-- 完整插入 INSERT INTO student VALUES(NULL, '张三', 'zhangsan@example.com', '2023-09-01', 'M'); -- 指定列插入(推荐) INSERT INTO student(name, email) VALUES('李四', 'lisi@example.com'); -- 批量插入(高效) INSERT INTO student(name, email) VALUES ('王五', 'wangwu@example.com'), ('赵六', 'zhaoliu@example.com');3.2 UPDATE操作的陷阱
UPDATE语句看似简单,但有几个常见陷阱:
- 忘记WHERE条件会导致全表更新:
-- 灾难性操作!会更新所有记录 UPDATE student SET gender = 'M'; -- 正确做法 UPDATE student SET gender = 'M' WHERE id = 1;- 基于当前值的更新:
-- 将分数低于60的学生加10分 UPDATE student SET score = score + 10 WHERE score < 60;- 多表关联更新:
UPDATE student s, class c SET s.class_name = c.name WHERE s.class_id = c.id;3.3 DELETE与TRUNCATE的区别
这两个命令都能清空表数据,但有本质区别:
| 特性 | DELETE | TRUNCATE |
|---|---|---|
| 分类 | DML | DDL |
| 可回滚 | 是 | 否 |
| 触发触发器 | 是 | 否 |
| 性能 | 逐行删除,较慢 | 直接删除表后重建,极快 |
| WHERE条件 | 支持 | 不支持 |
实际项目中,我建议:
- 需要条件删除时用DELETE
- 需要快速清空大表时用TRUNCATE
- 生产环境执行前务必先备份
4. 数据查询语言(DQL)全面掌握
4.1 SELECT语句执行顺序
理解SELECT语句的执行顺序是写出高效查询的关键:
- FROM:确定数据来源
- WHERE:初步筛选记录
- GROUP BY:分组
- HAVING:筛选分组
- SELECT:选择输出列
- ORDER BY:排序结果
- LIMIT:限制返回行数
常见错误是把HAVING当WHERE用,导致性能问题:
-- 低效写法 SELECT department, AVG(salary) FROM employees GROUP BY department HAVING department = 'IT'; -- 高效写法 SELECT department, AVG(salary) FROM employees WHERE department = 'IT' GROUP BY department;4.2 多表连接查询
JOIN是SQL最强大的特性之一,主要有以下几种:
- INNER JOIN:只返回匹配的行
SELECT s.name, c.class_name FROM student s INNER JOIN class c ON s.class_id = c.id;- LEFT JOIN:返回左表所有行,右表不匹配则为NULL
SELECT s.name, c.class_name FROM student s LEFT JOIN class c ON s.class_id = c.id;- RIGHT JOIN:返回右表所有行,左表不匹配则为NULL
- FULL JOIN:返回所有匹配和不匹配的行(MySQL不支持)
经验:在大型系统中,JOIN操作可能很耗资源。我曾优化过一个从15秒降到0.5秒的查询,关键是把多个JOIN拆分为更简单的查询。
4.3 子查询与CTE
子查询的几种常见用法:
- WHERE子句中的子查询:
SELECT name FROM student WHERE score > (SELECT AVG(score) FROM student);- FROM子句中的子查询:
SELECT dept, avg_salary FROM (SELECT department AS dept, AVG(salary) AS avg_salary FROM employees GROUP BY department) AS temp WHERE avg_salary > 5000;- 使用WITH子句的CTE(Common Table Expression):
WITH high_score_students AS ( SELECT * FROM student WHERE score > 90 ) SELECT * FROM high_score_students ORDER BY score DESC;CTE比子查询更易读,特别是处理多层嵌套时。
5. 数据控制语言(DCL)与事务管理
5.1 权限控制
DCL主要包含权限管理命令:
- 创建用户:
CREATE USER 'readonly'@'%' IDENTIFIED BY 'secure_password';- 授予权限:
GRANT SELECT ON school.* TO 'readonly'@'%';- 撤销权限:
REVOKE SELECT ON school.* FROM 'readonly'@'%';- 刷新权限:
FLUSH PRIVILEGES;安全提示:生产环境中应遵循最小权限原则,避免使用'%'作为主机名,并定期审查用户权限。
5.2 事务管理
事务的ACID特性:
- Atomicity(原子性)
- Consistency(一致性)
- Isolation(隔离性)
- Durability(持久性)
基本事务操作:
START TRANSACTION; UPDATE account SET balance = balance - 100 WHERE id = 1; UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT; -- 或 ROLLBACK;设置事务隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;我曾遇到一个典型问题:在高并发场景下,默认的REPEATABLE READ隔离级别导致大量锁等待。调整为READ COMMITTED后性能显著提升。
6. SQL优化实战技巧
6.1 索引优化
- 理解EXPLAIN:
EXPLAIN SELECT * FROM student WHERE name = '张三';关键列:
- type:ALL(全表扫描)→ index → range → ref → eq_ref → const
- possible_keys:可能使用的索引
- key:实际使用的索引
- rows:预估检查的行数
- 创建合适索引:
-- 单列索引 CREATE INDEX idx_score ON student(score); -- 复合索引(注意列顺序) CREATE INDEX idx_name_score ON student(name, score);- 避免索引失效:
- 不要在索引列上使用函数:
WHERE YEAR(create_time) = 2023→WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31' - 注意LIKE前缀:
WHERE name LIKE '%张'无法使用索引
6.2 查询重写技巧
- 用JOIN代替子查询:
-- 低效 SELECT name FROM student WHERE class_id IN (SELECT id FROM class WHERE type = 'A'); -- 高效 SELECT s.name FROM student s JOIN class c ON s.class_id = c.id WHERE c.type = 'A';- 分页优化:
-- 低效(偏移量大时) SELECT * FROM student ORDER BY id LIMIT 10000, 20; -- 高效(记住上一页最后一条记录的ID) SELECT * FROM student WHERE id > 10000 ORDER BY id LIMIT 20;- 避免SELECT *:
-- 不好 SELECT * FROM student; -- 好 SELECT id, name FROM student;6.3 数据库设计建议
- 规范化与反规范化:
- 规范化(减少冗余)适合OLTP系统
- 反规范化(适当冗余)适合OLAP系统
- 选择合适的数据类型:
- 能用INT就不用BIGINT
- 固定长度用CHAR,可变长度用VARCHAR
- 存储精确小数用DECIMAL,近似值用FLOAT/DOUBLE
- 大字段分离:
-- 将不常查询的大字段单独存放 CREATE TABLE student ( id INT PRIMARY KEY, name VARCHAR(50), -- 其他常用字段 ); CREATE TABLE student_profile ( student_id INT PRIMARY KEY, resume TEXT, photo LONGBLOB, FOREIGN KEY (student_id) REFERENCES student(id) );7. 常见SQL错误与解决方案
7.1 语法错误
- 忘记引号:
-- 错误 INSERT INTO student(name) VALUES(张三); -- 正确 INSERT INTO student(name) VALUES('张三');- 保留字冲突:
-- 错误(order是保留字) CREATE TABLE order (id INT); -- 正确 CREATE TABLE `order` (id INT);7.2 逻辑错误
- NULL值处理:
-- 错误(NULL比较不能用=) SELECT * FROM student WHERE score = NULL; -- 正确 SELECT * FROM student WHERE score IS NULL;- 日期范围:
-- 错误(可能漏掉23:59:59的数据) SELECT * FROM orders WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31'; -- 正确 SELECT * FROM orders WHERE order_date >= '2023-01-01' AND order_date < '2023-02-01';7.3 性能问题
- N+1查询问题:
// 伪代码:先查学生列表,再循环查每个学生的班级 List<Student> students = studentDao.findAll(); for (Student s : students) { s.setClass(classDao.findById(s.getClassId())); }解决方案:
- 使用JOIN一次性获取所有数据
- 使用批量查询(WHERE id IN (...))
- 过度使用OR:
-- 低效 SELECT * FROM products WHERE category = '电子' OR category = '家居' OR category = '图书'; -- 高效 SELECT * FROM products WHERE category IN ('电子', '家居', '图书');8. 高级SQL特性与应用
8.1 窗口函数
窗口函数是SQL高级特性,可以在不减少行数的情况下进行计算:
-- 计算每个学生的分数排名(同分同名次) SELECT name, score, RANK() OVER (ORDER BY score DESC) AS rank FROM student; -- 计算各部门工资排名 SELECT name, department, salary, DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank FROM employee;8.2 递归查询
处理树形结构数据:
WITH RECURSIVE org_tree AS ( -- 基础查询:找出顶级部门 SELECT id, name, parent_id, 1 AS level FROM department WHERE parent_id IS NULL UNION ALL -- 递归查询:找出子部门 SELECT d.id, d.name, d.parent_id, t.level + 1 FROM department d JOIN org_tree t ON d.parent_id = t.id ) SELECT * FROM org_tree ORDER BY level, id;8.3 JSON支持
现代数据库对JSON的支持:
-- MySQL JSON类型 CREATE TABLE product ( id INT PRIMARY KEY, name VARCHAR(100), attributes JSON ); -- 插入JSON数据 INSERT INTO product VALUES(1, '手机', '{ "color": "black", "storage": "128GB", "specs": ["5G", "NFC"] }'); -- 查询JSON字段 SELECT name, attributes->>"$.color" AS color, JSON_EXTRACT(attributes, "$.specs[0]") AS main_spec FROM product;8.4 全文检索
-- 创建全文索引 ALTER TABLE article ADD FULLTEXT INDEX ft_index (title, content); -- 全文搜索 SELECT * FROM article WHERE MATCH(title, content) AGAINST('数据库 优化' IN NATURAL LANGUAGE MODE);在实际项目中,我使用全文检索实现了高效的站内搜索功能,比LIKE查询快10倍以上。