SQL是通往数据世界的核心语言,掌握它不仅能让你高效操作数据库,更能深入理解数据存储、查询优化和系统架构的精髓。下面,我将按照认知规律,把学习过程拆解为三个逐步深入的阶段,为你详细讲解通用SQL知识。
引言:为什么SQL是数据领域的"硬通货"?
无论你是后端开发、数据分析师,还是专职DBA,SQL(Structured Query Language,结构化查询语言)都是必须熟练掌握的技能。它是操作关系型数据库的通用语言,几乎所有的业务系统最终都需要通过SQL与数据交互。
学习SQL就像学习一门新语言:先掌握基本词汇和语法(基础篇),再学习复杂句式和修辞(进阶篇),最后达到能写诗、能演讲的境界(精通篇)。本文将从这三个层次,带你一步步征服SQL。
基础篇------从零开始搭建数据操作能力
目标:掌握SQL的基本语法,能够独立完成单表的增删改查,理解数据库的核心概念。
1.1 SQL与数据库基础
关系型数据库(如MySQL、PostgreSQL、Oracle)将数据存储在由行和列组成的"表"中,表之间通过关系(如外键)连接。SQL就是用来与这些表对话的语言,它主要分为几类:
- DDL(数据定义语言):定义数据库和表的结构。
- DML(数据操作语言):操作表中的数据。
- DQL(数据查询语言):查询数据(通常归入DML,但常单独强调)。
- DCL(数据控制语言):管理权限。
书写规范:
- 关键字建议大写,表名/字段名小写,提高可读性。
- 每条语句以分号结尾。
- 使用缩进和空格让复杂查询更清晰。
1.2 DDL:搭建数据容器
数据库操作
sql
-- 创建数据库,指定字符集(避免中文乱码)
CREATE DATABASE school CHARACTER SET utf8mb4;
-- 查看所有数据库
SHOW DATABASES;
-- 选择当前数据库
USE school;
-- 删除数据库(生产环境务必谨慎!)
DROP DATABASE school;
表操作
sql
-- 创建学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增
stu_no VARCHAR(20) UNIQUE NOT NULL, -- 学号,唯一约束,非空
name VARCHAR(50) NOT NULL,
gender ENUM('M','F'),
birth_date DATE,
class_id INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 查看表结构
DESC student;
-- 修改表(添加字段)
ALTER TABLE student ADD COLUMN phone VARCHAR(20);
-- 修改表(修改字段类型)
ALTER TABLE student MODIFY COLUMN phone VARCHAR(30);
-- 删除表
DROP TABLE student;
1.3 DML:数据的增删改
插入数据
sql
-- 插入单条
INSERT INTO student (stu_no, name, gender, birth_date, class_id)
VALUES ('2024001', '张三', 'M', '2005-03-12', 101);
-- 插入多条
INSERT INTO student (stu_no, name, gender, birth_date, class_id) VALUES
('2024002', '李四', 'M', '2005-07-21', 101),
('2024003', '王芳', 'F', '2006-01-05', 102);
-- 从另一张表批量插入(常用于数据迁移)
INSERT INTO student_archive (stu_no, name)
SELECT stu_no, name FROM student WHERE class_id = 101;
更新数据
sql
-- 一定要加WHERE条件,否则全表更新!
UPDATE student SET class_id = 103 WHERE stu_no = '2024002';
删除数据
sql
-- 同样要加WHERE
DELETE FROM student WHERE stu_no = '2024003';
-- 删除所有数据但保留表结构(比DELETE快)
TRUNCATE TABLE student;
1.4 DQL:单表查询的核心语法
查询是SQL最常用也最灵活的部分,基本结构为:
sql
SELECT 字段1, 字段2
FROM 表名
[WHERE 条件]
[GROUP BY 分组字段]
[HAVING 分组后条件]
[ORDER BY 排序字段 [ASC|DESC]]
[LIMIT 起始偏移, 行数];
基础查询示例
sql
-- 查询所有字段
SELECT * FROM student;
-- 查询指定字段,并起别名
SELECT name AS 姓名, birth_date AS 出生日期 FROM student;
-- 去重查询
SELECT DISTINCT class_id FROM student;
-- 条件查询
SELECT * FROM student WHERE class_id = 101 AND gender = 'M';
-- 模糊查询(%代表任意多个字符,_代表单个字符)
SELECT * FROM student WHERE name LIKE '张%';
-- 范围查询
SELECT * FROM student WHERE birth_date BETWEEN '2005-01-01' AND '2005-12-31';
-- 排序
SELECT * FROM student ORDER BY birth_date DESC;
-- 限制返回行数(分页)
SELECT * FROM student LIMIT 0, 10; -- 第1页,每页10条
SELECT * FROM student LIMIT 10, 10; -- 第2页
常用函数
- 聚合函数 :
COUNT(*),SUM(score),AVG(age),MAX(salary),MIN(salary) - 字符串函数 :
CONCAT(first_name, last_name),SUBSTR(name,1,3),UPPER(email) - 日期函数 :
YEAR(birth_date),DATEDIFF(NOW(), birth_date) - 数学函数 :
ROUND(price,2),ABS(diff)
示例:统计每个班级的学生人数和平均年龄
sql
SELECT class_id,
COUNT(*) AS student_count,
AVG(TIMESTAMPDIFF(YEAR, birth_date, CURDATE())) AS avg_age
FROM student
GROUP BY class_id;
进阶篇------掌握多表关联与复杂数据处理
目标:理解关系数据库的"关系"本质,能够编写多表连接、子查询,掌握视图、索引基础,初步认识事务。
2.1 多表连接(JOIN)
当数据分散在不同表时,需要通过连接将它们组合起来。假设还有班级表 class:
sql
CREATE TABLE class (
id INT PRIMARY KEY,
class_name VARCHAR(50),
teacher_id INT
);
INNER JOIN(内连接)
只返回两表中匹配的行。
sql
SELECT s.name, c.class_name
FROM student s
INNER JOIN class c ON s.class_id = c.id;
LEFT JOIN(左连接)
返回左表所有行,右表若无匹配则补NULL。
sql
SELECT s.name, c.class_name
FROM student s
LEFT JOIN class c ON s.class_id = c.id; -- 即使学生没有班级(class_id为NULL),也会显示学生信息
RIGHT JOIN 与 FULL JOIN
RIGHT JOIN与LEFT JOIN方向相反。FULL JOIN(MySQL不支持,可用LEFT JOIN UNION RIGHT JOIN模拟)返回左右表所有行。
多表连接技巧
- 尽量使用表别名简化书写。
- 明确连接条件,避免产生笛卡尔积(无连接条件时两表行数相乘,结果巨大且无意义)。
2.2 子查询
在一个查询内部嵌套另一个查询。
标量子查询(返回单个值)
sql
-- 查询工资高于平均工资的员工
SELECT name, salary FROM employee
WHERE salary > (SELECT AVG(salary) FROM employee);
列子查询(返回一列)
sql
-- 查询在"技术部"的所有员工
SELECT name FROM employee
WHERE dept_id IN (SELECT id FROM department WHERE name = '技术部');
行子查询(返回一行多列)
sql
-- 查询与"张三"同部门同年龄的员工
SELECT * FROM employee
WHERE (dept_id, age) = (SELECT dept_id, age FROM employee WHERE name = '张三');
表子查询(返回多行多列,常用于FROM子句)
sql
-- 查询各部门平均工资,然后找出平均工资大于5000的部门
SELECT dept_id, avg_salary
FROM (SELECT dept_id, AVG(salary) AS avg_salary FROM employee GROUP BY dept_id) AS dept_avg
WHERE avg_salary > 5000;
EXISTS 与 NOT EXISTS
检查子查询是否返回任何行,通常用于关联子查询。
sql
-- 查询有员工的部门
SELECT * FROM department d
WHERE EXISTS (SELECT 1 FROM employee e WHERE e.dept_id = d.id);
2.3 分组与聚合进阶
HAVING 过滤分组
sql
-- 查询平均年龄大于30岁的班级
SELECT class_id, AVG(age) AS avg_age
FROM student
GROUP BY class_id
HAVING avg_age > 30;
ROLLUP 实现小计与总计
sql
-- 按班级和性别分组统计人数,同时生成班级小计和总计
SELECT class_id, gender, COUNT(*) AS cnt
FROM student
GROUP BY class_id, gender WITH ROLLUP;
2.4 视图(VIEW):虚拟表
视图是保存的查询逻辑,可以像表一样查询,但不存储数据。
sql
-- 创建视图:显示学生姓名、班级名和班主任
CREATE VIEW v_student_class AS
SELECT s.name AS student_name, c.class_name, t.name AS teacher_name
FROM student s
JOIN class c ON s.class_id = c.id
JOIN teacher t ON c.teacher_id = t.id;
-- 使用视图
SELECT * FROM v_student_class WHERE class_name = '高三(1)班';
2.5 索引基础
索引是提升查询速度的关键,但会降低写入性能。
sql
-- 创建普通索引
CREATE INDEX idx_stu_name ON student(name);
-- 创建唯一索引(自动保证唯一性)
CREATE UNIQUE INDEX idx_stu_no ON student(stu_no);
-- 创建联合索引(多个字段)
CREATE INDEX idx_class_age ON student(class_id, age);
-- 查看索引
SHOW INDEX FROM student;
-- 删除索引
DROP INDEX idx_stu_name ON student;
索引使用原则:
- 经常用于
WHERE、JOIN、ORDER BY的字段考虑建索引。 - 区分度低的字段(如性别)不适合建索引。
- 联合索引遵循最左前缀原则,查询条件必须包含索引的最左列才能用到索引。
2.6 事务基础
事务确保一组操作要么全部成功,要么全部失败。
sql
-- 开启事务
START TRANSACTION;
-- 执行操作
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 提交事务(成功)
COMMIT;
-- 如果出错,回滚事务
ROLLBACK;
事务的ACID特性:
- 原子性:事务不可分割。
- 一致性:数据从一种一致状态变为另一种一致状态。
- 隔离性:并发事务互不干扰。
- 持久性:提交后数据永久保存。
精通篇------深入性能调优与架构设计
目标:能够诊断并优化慢SQL,理解数据库底层机制,设计高可用、可扩展的数据架构。
3.1 查询优化与执行计划
使用 EXPLAIN 分析查询
sql
EXPLAIN SELECT * FROM student WHERE name = '张三';
关键输出列解读:
- type :访问类型,从好到坏依次为
system>const>eq_ref>ref>range>index>ALL(全表扫描)。 - possible_keys:可能用到的索引。
- key:实际用到的索引。
- rows:预估扫描的行数。
- Extra :额外信息,如
Using index(覆盖索引)、Using where、Using filesort(需要优化排序)。
常见优化手段
- **避免 SELECT ***:只取需要的列,减少IO。
- 避免索引列上使用函数或计算 :
WHERE YEAR(birth_date)=2005应改为WHERE birth_date BETWEEN '2005-01-01' AND '2005-12-31'。 - 使用覆盖索引:索引包含了查询所需的所有字段,避免回表。
- 优化排序 :
ORDER BY字段尽量与索引顺序一致。 - 拆分大查询:将复杂关联拆分成多次简单查询,有时反而更快。
3.2 索引深入
索引失效场景
- 对索引列使用
LIKE '%xxx'(左模糊)。 - 对索引列使用
!=或<>(可能失效,取决于数据分布)。 - 联合索引不满足最左前缀。
- 数据类型隐式转换(如字符串字段不写引号)。
索引选择性与基数
- 选择性 = 不同值的数量 / 总行数。选择性越高,索引越有效。
- 对于低基数字段(如性别),索引效果差,可能不如全表扫描。
3.3 事务隔离级别与锁
四种隔离级别
- READ UNCOMMITTED:脏读、不可重复读、幻读都可能发生。
- READ COMMITTED:避免脏读,但可能不可重复读、幻读(Oracle默认)。
- REPEATABLE READ:避免脏读和不可重复读,但可能幻读(MySQL InnoDB默认,通过间隙锁一定程度上避免幻读)。
- SERIALIZABLE:全部避免,性能最低。
设置隔离级别:
sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
锁机制
- 共享锁 :
SELECT ... LOCK IN SHARE MODE(读锁,允许其他事务读,但阻止写)。 - 排他锁 :
SELECT ... FOR UPDATE(写锁,阻止其他事务读写)。 - 行锁 、表锁 、间隙锁(InnoDB特有,锁定范围防止幻读)。
死锁:两个事务互相等待对方释放锁,数据库会自动检测并回滚其中一个。
3.4 高级功能
分区表
将大表物理分割为多个小分区,提升查询维护效率。
sql
CREATE TABLE orders (
id INT,
order_date DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
存储过程与触发器
-
存储过程 :预编译的SQL集合,减少网络开销,提高复用性。
sqlDELIMITER // CREATE PROCEDURE GetStudentsByClass(IN c_id INT) BEGIN SELECT * FROM student WHERE class_id = c_id; END // DELIMITER ; CALL GetStudentsByClass(101); -
触发器 :在插入、更新、删除时自动执行。
sqlCREATE TRIGGER before_student_insert BEFORE INSERT ON student FOR EACH ROW SET NEW.created_at = NOW();
窗口函数(SQL标准,MySQL 8.0+支持)
实现排名、移动平均等复杂分析。
sql
SELECT name, salary,
RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS dept_rank
FROM employee;
3.5 高可用与分库分表
读写分离
- 主库 处理写操作,从库处理读操作,通过主从复制同步数据。
- 应用层或中间件(如ProxySQL)路由SQL。
分库分表
- 垂直拆分:按业务将字段拆分到不同库表(如将用户基础信息和扩展信息分开)。
- 水平拆分:按某种规则(如用户ID取模)将数据分散到多个结构相同的表或库中。
- 常用中间件:ShardingSphere、MyCAT。
3.6 监控与诊断
DBA的日常工作离不开监控:
- 慢查询日志 :记录执行时间超过
long_query_time的SQL,定期分析优化。 - SHOW PROCESSLIST:查看当前正在执行的线程,快速定位锁等待或长事务。
- 性能视图 :如MySQL的
information_schema、performance_schema,Oracle的v$视图。 - 工具:Prometheus + Grafana、pt-query-digest(Percona Toolkit)。
SQL 快速参考
| SQL 语句 | 语法 | 说明 |
|---|---|---|
| AND / OR | SELECT column_name(s) FROM table_name WHERE condition AND/OR condition | AND:表示逻辑与OR:表示逻辑或 |
| ALTER TABLE | ALTER TABLE table_name ADD column_name datatype ALTER TABLE table_name DROP COLUMN column_name | 用于修改现有表的结构,添加或删除列。 |
| AS (alias) | SELECT column_name AS column_alias FROM table_name SELECT column_name FROM table_name AS table_alias | 用于为列或表指定别名。 |
| BETWEEN | SELECT column_name(s) FROM table_name WHERE column_name BETWEEN value1 AND value2 | 用于筛选在指定范围内的记录。 |
| CREATE DATABASE | CREATE DATABASE database_name | 用于创建新数据库。 |
| CREATE TABLE | CREATE TABLE table_name (column_name1 data_type, column_name2 data_type, ...) | 用于创建新表,定义表的列和数据类型。 |
| CREATE INDEX | CREATE INDEX index_name ON table_name (column_name) CREATE UNIQUE INDEX index_name ON table_name (column_name) | 用于在表的列上创建索引,以加速查询。 |
| CREATE VIEW | CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition | 用于创建视图,以保存复杂查询的结果。 |
| DELETE | DELETE FROM table_name WHERE some_column=some_value DELETE FROM table_name DELETE * FROM table_name | 用于删除表中的记录,DELETE FROM table_name 和 DELETE * FROM table_name 会删除所有记录。 |
| DROP DATABASE | DROP DATABASE database_name | 用于删除数据库。 |
| DROP INDEX | DROP INDEX table_name.index_name (SQL Server) DROP INDEX index_name ON table_name (MS Access) DROP INDEX index_name (DB2/Oracle) ALTER TABLE table_name DROP INDEX index_name (MySQL) | 用于删除表上的索引。 |
| DROP TABLE | DROP TABLE table_name | 用于删除表及其所有数据。 |
| GROUP BY | SELECT column_name, aggregate_function(column_name) FROM table_name WHERE column_name operator value GROUP BY column_name | 用于按一个或多个列对结果集进行分组。 |
| HAVING | SELECT column_name, aggregate_function(column_name) FROM table_name WHERE column_name operator value GROUP BY column_name HAVING aggregate_function(column_name) operator value | 用于对分组后的结果集进行过滤。 |
| IN | SELECT column_name(s) FROM table_name WHERE column_name IN (value1, value2, ...) | 用于筛选匹配集合中某一值的记录。 |
| INSERT INTO | INSERT INTO table_name VALUES (value1, value2, ...) INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...) | 用于向表中插入新记录。 |
| INNER JOIN | SELECT column_name(s) FROM table_name1 INNER JOIN table_name2 ON table_name1.column_name=table_name2.column_name | 用于返回两个表中匹配的记录。 |
| LEFT JOIN | SELECT column_name(s) FROM table_name1 LEFT JOIN table_name2 ON table_name1.column_name=table_name2.column_name | 用于返回左表中的所有记录和右表中的匹配记录。 |
| RIGHT JOIN | SELECT column_name(s) FROM table_name1 RIGHT JOIN table_name2 ON table_name1.column_name=table_name2.column_name | 用于返回右表中的所有记录和左表中的匹配记录。 |
| FULL JOIN | SELECT column_name(s) FROM table_name1 FULL JOIN table_name2 ON table_name1.column_name=table_name2.column_name | 用于返回两个表中的所有记录,不论是否匹配。 |
| LIKE | SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern | 用于筛选匹配特定模式的记录。 |
| ORDER BY | SELECT column_name(s) FROM table_name ORDER BY column_name [ASC/DESC] | 用于对结果集进行排序。ASC 表示升序排列(默认),DESC 表示降序排列。 |
| SELECT | SELECT column_name(s) FROM table_name | 用于从表中选择数据。 |
| SELECT | SELECT * FROM table_name | 用于选择表中的所有列。 |
| SELECT DISTINCT | SELECT DISTINCT column_name(s) FROM table_name | 用于返回唯一不同的值。 |
| SELECT INTO | SELECT * INTO new_table_name [IN externaldatabase] FROM old_table_name SELECT column_name(s) INTO new_table_name [IN externaldatabase] FROM old_table_name | 用于从一个表中选择数据并插入到新表中。 |
| SELECT TOP | SELECT TOP numbe/percent column_name(s) FROM table_name ORDER BY column_name [ASC/DESC] | 从表中返回前指定数量的记录,可以指定绝对数量或百分比。 |
| TRUNCATE TABLE | TRUNCATE TABLE table_name | 用于删除表中的所有数据,但不删除表结构。 |
| UNION | SELECT column_name(s) FROM table_name1 UNION SELECT column_name(s) FROM table_name2 | 用于合并两个或多个 SELECT 语句的结果集,不包含重复记录。 |
| UNION ALL | SELECT column_name(s) FROM table_name1 UNION ALL SELECT column_name(s) FROM table_name2 | 用于合并两个或多个 SELECT 语句的结果集,包含重复记录。 |
| UPDATE | UPDATE table_name SET column1=value, column2=value, ... WHERE some_column=some_value | 用于修改表中的现有记录。 |
| WHERE | SELECT column_name(s) FROM table_name WHERE column_name operator value | 用于过滤记录,指定查询条件。 |
结语:SQL之路,行则将至
从基础的增删改查,到复杂的查询优化与架构设计,SQL的学习是一个循序渐进、理论与实践相结合的过程。在日常学习工作过程中建议:
- 多写多练:在实际业务中尝试不同的写法,对比执行计划。
- 关注原理:理解数据库的底层实现(如B+树、锁机制),能让你在调优时更有底气。
- 拥抱变化:数据库技术日新月异,NewSQL、云原生数据库不断涌现,但核心SQL知识永不过时。