数据模型与 SQL 基础
什么是数据模型
数据模型是用来组织和描述数据结构的方式,关系型数据库使用关系模型。
核心概念包含:
-
表 (Table)
现实世界实体的抽象表示
以行、列描述字段
-
行 (Row)
一条完整的数据记录
-
列 (Column)
数据字段,例如 name 列,age 列,每列有数据类型
-
主键 (Primary Key)
唯一标识,一行数据,不可重复,不能为空
-
外键 (Forrign Key)
用与表示表之间的关系,例如用户表和订单表
user_id INT, FOREIGN KEY (user_id) REFERENCES users(id)
-
索引 (Index)
用于提高查询速度的结构
CREATE INDEX idx_email ON users(email)
SQL 语言分类
SQL(Structured Query Language)包括四大类:
DDL (数据定义语言)
用于操作库和表结构:
-
CREATE: 创建数据库,表
-
ALTER: 修改表结构
-
DROP: 删除数据库,表
-
RENAME: 重命名表
特点:立即生效(自动提交,不能回滚),立即操作结构而不是数据
数据库
- 创建数据库:
1
|
CREATE DATABASE company;
|
- 查看所有数据库:
- 切换数据库:
- 删除数据库:
- 判断不存在再创建:
1
|
CREATE DATABASE IF NOT EXISTS company;
|
数据表
表结构是数据库设计的核心。
1
2
3
4
5
6
7
8
|
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
position VARCHAR(50),
salary DECIMAL(10,2) DEFAULT 0,
hire_date DATE,
email VARCHAR(100) UNIQUE
);
|
基础数据类型
数值类型:
| 类型 |
字节 |
示例 |
说明 |
| TINYINT |
1 |
127 / -128 |
小整数,取值范围 -128~127 |
| SMALLINT |
2 |
32767 |
较小整数 |
| MEDIUMINT |
3 |
8388607 |
中等整数 |
| INT / INTEGER |
4 |
2,147,483,647 |
最常用整型 |
| BIGINT |
8 |
9,223,372,036,854,775,807 |
大整数(如用户 ID) |
| FLOAT |
4 |
3.14 |
单精度浮点数(不精确) |
| DOUBLE |
8 |
3.1415926535 |
双精度浮点数(不精确) |
| DECIMAL(M,D) |
可变 |
123.45 |
精确小数,金额必用 |
字符串类型:
| 类型 |
最大长度 |
示例 |
说明 |
| CHAR(n) |
固定 n 字节 |
‘abc’ |
固定长度字符串(效率高) |
| VARCHAR(n) |
可变 |
‘hello’ |
最常用变长字符串 |
| TINYTEXT |
255 字节 |
文本 |
小文本 |
| TEXT |
65KB |
长文内容 |
一般文本字段 |
| MEDIUMTEXT |
16MB |
大文本 |
不建议频繁查询 |
| LONGTEXT |
4GB |
超大文本 |
存储日志等 |
| TINYBLOB |
255 字节 |
二进制 |
小型二进制数据 |
| BLOB |
65KB |
二进制 |
存储图片/文件 |
| MEDIUMBLOB |
16MB |
二进制 |
较大文件 |
| LONGBLOB |
4GB |
二进制 |
超大文件 |
| ENUM(…) |
1 字节 |
ENUM(‘male’,‘female’) |
枚举类型(可读性高) |
| SET(…) |
1~8 字节 |
SET(‘a’,‘b’) |
多选集合 |
日期与时间类型:
| 类型 |
字节 |
示例 |
说明 |
| DATE |
3 |
‘2024-01-01’ |
仅日期 |
| TIME |
3 |
‘12:30:00’ |
时间 |
| YEAR |
1 |
2024 |
年份 |
| DATETIME |
5 |
‘2024-01-01 12:30:00’ |
无时区影响,常用 |
| TIMESTAMP |
4 |
‘2024-01-01 12:30:00’ |
受时区影响,可自动填充 |
查看表结构
1
2
3
4
5
|
DESC employess;
-- 或者
SHOW CREATE TABLE employess;
|
修改表结构
- 添加字段
1
2
3
4
5
|
ALTER TABLE employess
ADD phone VARCHAR(50);
ALTER TABLE employess
ADD address VARCHAR(100) AFTER name; -- 添加到某字段后
|
- 修改字段类型
1
2
|
ALTER TABLE employess
MODIFY salary DECIMAL(12, 2);
|
- 修改字段名 + 类型
1
2
|
ALTER TABLE employees
CHANGE phone mobile VARCHAR(30);
|
- 删除字段
1
2
|
ALTER TABLE employees
DROP COLUMN address;
|
- 重命名表
1
|
RENAME TABLE employees TO staff;
|
- 添加约束
1
2
3
4
5
6
|
ALTER TABLE employees
ADD UNIQUE (email);
ALTER TABLE employees
ADD CONSTRAINT fk_user
FOREIGN KEY (user_id) REFERENCES users(id);
|
删除表
1
2
3
|
DROP TABLE employees;
DROP TABLE IF EXISTS employees;
|
DML (数据操作语言)
用于对表中的数据进行增删改:
- INSERT: 插入数据
1
2
3
4
5
6
|
INSERT INTO employees (name, position, salary)
VALUES ('Alice', 'Engineer', 12000);
-- 也可不指定字段
INSERT INTO employees
VALUES (1, 'Bob', 'Manager', 18000, '2023-01-10');
|
可同时插入多条记录
1
2
3
4
5
|
INSERT INTO employees (name, position, salary)
VALUES
('Tom', 'HR', 9000),
('Lucy', 'Engineer', 13000),
('Ken', 'Designer', 11000);
|
也可使用默认值插入
1
2
|
INSERT INTO employees (name, position)
VALUES ('Jerry', DEFAULT);
|
从查询结果插入
1
2
3
4
|
INSERT INTO developer (name, salary)
SELECT name, salary
FROM employees
WHERE position = 'Engineer';
|
- UPDATE: 更新数据
1
2
3
4
5
6
7
8
9
10
11
12
13
|
UPDATE employees
SET salary = 15000
WHERE name = 'Alice';
-- 更新多字段
UPDATE employees
SET position = 'Senior Engineer',
salary = salary + 2000
WHERE id = 1;
-- 若不使用 WHERE 即为全表更新
UPDATE employees
SET salary = salary + 1000;
|
- DELETE: 删除数据
1
2
3
4
5
6
7
8
9
|
DELETE FROM employees
WHERE id = 3; -- 删除单条
DELETE FROM employees
WHERE salary < 5000; -- 删除多条
DELETE FROM employees; -- 危险,全表删除
TRUNCATE TABLE employees; -- 清空表(不记录逐行删除日志)
|
DQL (数据查询语言)
SELECT 查询记录
基础查询
- 查询所有字段
1
|
SELECT * FROM employees;
|
- 查询指定字段
1
|
SELECT name, salary FROM employees;
|
- 查询计算结果
1
2
|
SELECT name, salary * 12 annual_salary
FROM employees;
|
- 去重
1
|
SELECT DISTINCT position FROM employees;
|
条件查询
- 比较运算符
1
2
3
|
SELECT * FROM employees WHERE salary > 10000;
SELECT * FROM employees WHERE age >= 30;
SELECT * FROM employees WHERE name <> 'Alice';
|
- 逻辑运算符
AND OR NOT
1
2
3
4
5
6
7
8
|
SELECT * FROM employees
WHERE position = 'Engineer' AND salary > 12000;
SELECT * FROM employees
WHERE position = 'Engineer' OR position = 'Designer';
SELECT * FROM employees
WHERE NOT (salary > 20000);
|
- 范围查询
1
2
|
SELECT * FROM employees
WHERE salary BETWEEN 10000 AND 20000;
|
- 集合查询
1
2
|
SELECT * FROM employees
WHERE position IN ('Engineer', 'Manager', 'Designer');
|
- 模糊查询
_ 表示一位模糊, % 多位模糊
1
2
3
|
SELECT * FROM employees WHERE name LIKE 'A%'; -- A开头
SELECT * FROM employees WHERE name LIKE '%son'; -- 以 son 结尾
SELECT * FROM employees WHERE name LIKE '%a%'; -- 含 a
|
- NULL 判断
1
2
|
SELECT * FROM employees WHERE email IS NULL;
SELECT * FROM employees WHERE email IS NOT NULL;
|
聚合函数
- COUNT 计数
- SUM 求和
- AVG 平均值
- MAX/MIN 最大最小值
聚合函数会忽略 NULL 的数据
分组查询
GROUP BY 用与按字段分组,可结合聚合函数使用
示例 1: 统计各职位的平均薪资
1
2
3
|
SELECT position, AVG(salary) AS avg_salary
FROM employees
GROUP BY position;
|
HAVING 对分组结果过滤
1
2
3
4
|
SELECT position, AVG(salary) AS avg_salary
FROM employees
GROUP BY position
HAVING avg_salary > 15000;
|
排序查询
ORDER BY 支持升降序
1
2
|
SELECT * FROM employees
ORDER BY position ASC, salary DESC;
|
按顺序进行
分页查询
通过 LIMIT 实现
1
2
3
|
LIMIT offset, size;
LIMIT size;
|
SQL 的执行顺序
1
|
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
|
DCL (数据控制语言)
主要包含两类操作
用户管理
- 创建用户
1
2
3
|
CREATE USER 'username'@'host' IDENTIFIED BY 'password';
-- localhost 为本机,% 为任意 IP
|
- 查看现有用户
1
|
SELECT user, host FROM mysql.user;
|
- 修改密码
1
|
ALTER USER 'dev'@'%' IDENTIFIED BY 'newpassword';
|
- 删除用户
权限管理
| 权限 |
说明 |
| SELECT |
查询数据 |
| INSERT |
插入数据 |
| UPDATE |
更新数据 |
| DELETE |
删除数据 |
| CREATE |
创建数据库/表 |
| DROP |
删除数据库/表 |
| ALTER |
修改表结构 |
| INDEX |
创建索引 |
| ALL PRIVILEGES |
所有权限(不包含 GRANT 权限) |
| GRANT OPTION |
允许把自己的权限再授予别人 |
- 授权
1
2
3
4
5
6
7
|
-- 对 company 的所有表,赋予所有权限
GRANT ALL PRIVILEGES ON company.* TO 'dev'@'%';
-- 授予部分权限
GRANT SELECT, INSERT, UPDATE
ON company.employees
TO 'dev'@'%';
|
- 取消权限
1
2
3
4
5
6
7
|
-- 部分撤销
REVOKE INSERT, UPDATE
ON company.employees
FROM 'dev'@'%';
-- 全部撤销
REVOKE ALL PRIVILEGES ON company.* FROM 'dev'@'%';
|
内置函数
字符串函数
- LENGTH/CHAR_LENGTH
获取长度
1
2
|
SELECT LENGTH('Hello'); -- 字节长度
SELECT CHAR_LENGTH('Hello'); -- 字符长度
|
注意:LENGTH 获取中文字符时,一个中文等于 3 的长度
- CONCAT
拼接字符串
1
2
|
SELECT CONCAT(first_name, ' ', last_name) AS fullname
FROM users;
|
- SUBSTRING/LEFT/RIGHT
截取字符串
1
2
3
|
SELECT SUBSTRING('HelloWorld', 1, 5); -- Hello
SELECT LEFT('HelloWorld', 5); -- Hello
SELECT RIGHT('HelloWorld', 5); -- World
|
- REPLACE
替换字符串
1
|
SELECT REPLACE('2024-01-01', '-', '/') -- 2024/01/01
|
- UPPER/LOWER
大小写转换
1
2
|
SELECT UPPER('hello'); -- HELLO
SELECT LOWER('Hello'); -- hello
|
- TRIM/LTRIM/RTRIM
去除空格
1
2
3
|
SELECT TRIM(' hi '); -- 'hi'
SELECT LTRIM(' hi'); -- 'hi'
SELECT RTRIM('hi '); -- 'hi'
|
数值函数
- ABS
绝对值
- ROUND/CEIL/FLOOR
1
2
3
|
SELECT ROUND(3.14159, 2); -- 3.14
SELECT CEIL(3.1); -- 4
SELECT FLOOR(3.9); -- 3
|
- RAND
随机数
1
2
|
SELECT RAND(); -- 0~1 随机小数
SELECT RAND()*100; -- 0~100
|
- MOD
取模
1
|
SELECT MOD(10, 3); -- 1
|
日期与时间函数
- NOW/CURDATE/CURTIME
1
2
3
|
SELECT NOW(); -- 当前日期 + 时间
SELECT CURDATE(); -- 当前日期
SELECT CURTIME(); -- 当前时间
|
- DATE_FORMAT
格式化日期
1
|
SELECT DATE_FORMAT(NOW(), '%Y/%m/%d %H:%i:%s');
|
- DATEDIFF/TIMESTAMPDIFF
计算时间差值
天数差
1
|
SELECT DATEDIFF('2024-02-01', '2024-01-01'); -- 31
|
任意单位差
1
|
SELECT TIMESTAMPDIFF(unit, datetime1, datetime2)
|
- DATE_ADD/DATE_SUB
1
2
|
SELECT DATE_ADD('2024-01-01', INTERVAL 30 DAY);
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);
|
流程控制函数
- IF 条件判断
1
2
3
|
SELECT name,
IF(salary > 10000, 'High', 'Low') AS salary_level
FROM employees;
|
- CASE WHEN
1
2
3
4
5
6
7
|
SELECT name,
CASE
WHEN salary >= 20000 THEN 'A'
WHEN salary >= 15000 THEN 'B'
ELSE 'C'
END AS grade
FROM employees;
|
JSON 函数
- 获取 JSON 中的字段
1
2
3
4
|
SELECT JSON_EXTRACT(info, '$.age') FROM users;
-- 简写
SELECT info->'$.age' FROM users;
|
- 修改 JSON
1
|
SELECT JSON_SET(info, '$.age', 30) FROM users;
|
- 判断是否合法
1
|
SELECT JSON_VALID(info) FROM users;
|
其他常用函数
- COALESCE
返回第一个非 NULL 值
1
|
SELECT COALESCE(nickname, username, 'unknown') AS display_name FROM users;
|
约束
约束是保证数据正确性的关键机制
约束目的:
约束类型
| 约束类型 |
作用 |
典型关键字 |
| 主键约束(PRIMARY KEY) |
唯一标识每行记录 |
PRIMARY KEY |
| 唯一约束(UNIQUE) |
字段值不能重复 |
UNIQUE |
| 非空约束(NOT NULL) |
字段不能为空 |
NOT NULL |
| 默认值约束(DEFAULT) |
没填值时自动填充 |
DEFAULT |
| 外键约束(FOREIGN KEY) |
保证表之间引用关系 |
FOREIGN KEY |
| 自增约束(AUTO_INCREMENT) |
自动编号 |
AUTO_INCREMENT |
主键约束
主键用于唯一表示一行记录,不能重复,不能为 NULL
1
2
3
4
|
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT, -- 通常搭配自增使用
name varchar(50)
);
|
唯一约束
保证某列的数据不重复
1
2
3
4
5
6
7
8
|
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE
);
-- 可多个字段联合唯一
UNIQUE (user_id, role_id)
|
非空约束
字段必须有值,不能为 NULL
1
|
name VARCHAR(50) NOT NULL
|
默认值约束
提供默认值,用户不填时自动赋值
1
|
status VARCHAR(20) DEFAULT 'active'
|
外键约束
外间保证表与表之间关系正确
1
2
3
4
5
|
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
FOREIGN KEY (user_id) REFERENCES users(id);
);
|
外键约束级联行为
| 行为 |
说明 |
| CASCADE |
父表删除/更新 → 子表同步删除/更新 |
| SET NULL |
父表删除/更新 → 子表字段置为 NULL |
| RESTRICT |
不允许删除父表数据(默认行为) |
| NO ACTION |
与 RESTRICT 类似 |
综合示例
1
2
3
4
5
6
7
|
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) UNIQUE NOT NULL,
age INT DEFAULT 18,
created_ad DATETIME DEFAULT CURRENT_TIMESTAMP
);
|
修改约束
- 唯一约束
1
2
3
|
ALTER TABLE users ADD UNIQUE (email); -- 添加
ALTER TABLE users DROP INDEX email; -- 删除
|
- 外键
1
2
3
4
5
|
ALTER TABLE orders
ADD CONSTRAINT fk_user
FOREIGN KEY (user_id) REFERENCES users(id); -- 添加
ALTER TABLE orders DROP FOREIGN KEY fk_user; -- 删除
|
多表查询(联表 & 子查询)
多表查询的核心问题是:当业务数据分散在多张表里时,怎么把他们组合起来查
多表关系
- 一对多
例子:一个用户会有多个订单。
1
|
users.id <- orders.user_id;
|
- 一对一
例子:用户与用户详情表
- 多对多
例子:学生和课程
通常使用一张中间表
1
|
student_course(student_id, course_id)
|
JOIN(连接查询)
JOIN 是横向拓展表,通过关联字段将数据拼在一起。
1
2
|
users(id, name)
orders(id, user_id. amount)
|
内连接(INNER JOIN)
只保留两张表都匹配到的数据
1
2
3
4
|
SELECT u.name, o.amount
FROM users AS u
INNER JOIN orders AS o
ON u.id = o.user_id;
|
左右两侧相交的部分。
左连接
保留左表全部数据,左表的记录将会全部表示出来,而右表只显示符合搜索条件的记录,右表记录不足的地方均为 NULL
1
2
3
4
|
SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id;
|
右连接
同上,保留的是右表
自连接
用于树形结构(员工-上级)
城市 parent_id 结构
示例(员工表):
1
|
employees(id, name, manager_id)
|
查询员工及其上级名称:
1
2
3
4
|
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m
ON e.manager_id = m.id;
|
并集(UNION/UNION ALL)
把多条查询的结果上下拼在一起
- UNION (去重)
1
2
3
|
SELECT id FROM t1
UNION
SELECT id FROM t2;
|
- UNION ALL (不去重, 性能更高)
1
2
3
|
SELECT id FROM t1
UNION ALL
SELECT id FROM t2;
|
子查询
子查询就是一条查询套在另一条查询内部
子查询可能出现位置
- WHERE 中
- FROM 中
- SELECT 后的字段位置
标量子查询(返回一个值)
例子:查金额 = 全库最大订单金额的订单
1
2
3
4
5
|
SELECT *
FROM orders
WHERE amount = (
SELECT MAX(amount) FROM orders
);
|
列子查询(返回多行一列)
例子:查所有下单用户
1
2
3
4
5
|
SELECT *
FROM users
WHERE id IN {
SELECT user_id FROM orders
};
|
行子查询(返回一整行)
1
2
3
4
5
|
SELECT *
FROM users
WHERE (id, name) = (
SELECT id, name FROM users WHERE id = 1
);
|
表子查询(FROM 中当作一张表)
例子:先查出总金额大于 100 的订单,再按用户统计数量:
1
2
3
4
5
6
7
|
SELECT t.user_id, COUNT(*) AS cnt
FROM (
SELECT user_id, amount
FROM orders
WHERE amount > 100
) AS t
GROUP BY t.user_id;
|
常用于:
多表查询练习
- 所有用户及其订单金额(无论是否下单)
1
2
3
4
|
SELECT u.id, u.name, o.amount
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id;
|
- 查询下单金额最高的用户
1
2
3
4
|
SELECT u.name, o.amount
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.amount = (SELECT MAX(amount) FROM orders);
|
- 查询每个用户总订单金额
1
2
3
4
|
SELECT u.name, SUM(o.amount) AS total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;
|
事务
什么是事务:
一组要么全部成功,要么全部失败的操作。
SQL 中一次事务通常由多条 DML 语句组成:
事务保证:
要么所有语句都完成,要么全部回滚到操作前的状态。
为什么需要事务
例如用户转账,用户 A 扣除 100 元,用户 B 才能增加 100 元。
如果只执行了第一步,第二步失败了,会发生数据错乱。
所以必须把两步放进一个事务:
-
两句 SQL 都成功 → 提交(COMMIT)
-
任意一句失败 → 回滚(ROLLBACK)
事务基本语法
1
2
3
4
5
6
|
START TRANSACTION; -- 也可以使用 BEGIN
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
|
如果需要撤销:
ACID 四大特性
| 特性 |
解释 |
示例 |
| A 原子性 (Atomicity) |
要么全做,要么全不做 |
转账两个 UPDATE 要作为一个整体 |
| C 一致性 (Consistency) |
事务前后数据保持业务一致规则 |
A+B 余额保持不变 |
| I 隔离性 (Isolation) |
多个事务彼此互不影响 |
多人同时转账不冲突 |
| D 持久性 (Durability) |
提交后数据永久落盘 |
服务宕机也保留结果 |
并发事务问题
当多个事务同时执行时,会产生冲突问题。
- 脏读
一个事务读到了另一个事务未提交的数据
例子:
事务 A 修改余额 → 未提交
事务 B 读到了修改后的余额
但 A 回滚了
→ B 读到的是不存在的数据
- 不可重复读
同一个事务的两次读取,结果不一致
- 幻读
同一个事务多次读取,出现了多出来或少的数据行
-
事务 A:SELECT * FROM orders WHERE amount > 100(查到 5 条)
-
事务 B:插入一条新订单 amount=200;COMMIT
-
事务 A 再查:出现 6 条
→ 多了一条 “幻影记录”
MySQL 四大隔离级别
| 隔离级别 |
脏读 |
不可重复读 |
幻读 |
| READ UNCOMMITTED |
✔ |
✔ |
✔ |
| READ COMMITTED (RC) |
✘ |
✔ |
✔ |
| REPEATABLE READ (RR)(默认) |
✘ |
✘ |
✔* |
| SERIALIZABLE |
✘ |
✘ |
✘ |
RR 下 MySQL 通过 间歇锁 + MVCC 解决大部分幻读问题(但仍可能出现)
如何查看和修改隔离级别
查看:
1
|
SELECT @@transaction_isolation;
|
设置:
1
|
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
|
系统级:
1
|
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
|
MySQL 默认隔离
默认隔离级别为:REPEATABLE READ
MySQL 在 RR 下的行为:
- 不会脏读(有 MVCC)
- 不会不可重复读(使用 READ View)
- 绝大多数场景避免幻读
- 幻读只可能在 当前读(SELECT … FOR UPDATE)场景发生
当前读 vs 快照读(MVCC)
InnoDB 事务有两种读取方式
| 类型 |
语句 |
特点 |
| 快照读 Snapshot Read |
SELECT |
不加锁,使用 MVCC |
| 当前读 Current Read |
SELECT … FOR UPDATE / LOCK IN SHARE MODE / UPDATE / DELETE |
加锁,读取最新版本 |
快照读使用历史版本链(undo log),能避免不可重复读
事务的提交模式(自动提交)
MySQL 默认自动提交:
1
|
SHOW VARIABLES LIKE 'autocommit';
|
一般自动提交为 1,即每条语句都是一个独立事务。
若要手动控制事务,需要:
事务使用最佳实践
-
一定要加 WHERE
否则会全表更新,且无法回滚数据快照。
-
不要在长事务中执行大量 SELECT
长事务会:
-
占用大量 undo log
-
阻塞 purge 清理
-
让数据库膨胀
-
尽量让事务短小,快速完成
-
避免在事务中混入用户交互(例如等待用户输入)