Mysql 基础
文章目录
- 执行顺序
- 对照图
- 完整逻辑顺序
- 综合案例
- DCL
- 用户管理
- 权限控制
- 约束 Constraint
- 根据案例理解约束
- 外键
- 外键语法
- 外键约束
- 子查询
- 行子查询
- 表子查询
- 事物 Transaction
- 四大特性 ACID
- 并发事务问题
- 脏读
- 不可重复读
- 幻读
- 对比
- 事务隔离级别
- 参考
执行顺序
对照图
编写顺序和逻辑执行顺序的对照图
书写顺序 (写法) | 关键子句 | 逻辑执行顺序 | 作用说明 |
---|---|---|---|
1 | SELECT | 6 | 选出要返回的列或表达式,计算派生列、别名、聚合函数 |
2 | FROM | 1 | 指定数据源表、视图、子查询;首先生成基表 / 视图的行集 |
3 | JOIN … ON | 1 (与 FROM 同阶段) | 多表连接;先做笛卡尔积,再按 ON 条件过滤 |
4 | WHERE | 2 | 行过滤(非聚合条件);只保留满足条件的行 |
5 | GROUP BY | 3 | 分组;把行集按键分桶,生成每组一条记录 |
6 | HAVING | 4 | 组过滤(聚合后的条件) |
7 | ORDER BY | 7 | 排序;对最终结果集排序,默认升序 (ASC) |
8 | LIMIT / OFFSET | 8 | 取前 N 行或做分页;在排序后截取 |
完整逻辑顺序
- FROM 含 JOIN … ON …
- 先确定数据源,生成初步行集。
- 多表连接时先做笛卡尔积,再用
ON
条件做行过滤。 - 此阶段还会解析并执行子查询 / 派生表(即 (SELECT …) AS t)。
- WHERE
- 对每一行执行条件判断,结果为 TRUE 才能进入下一阶段
- 只能使用原始列,不能使用后面
SELECT
中的别名,此时别名尚未生成
- GROUP BY
- 对
WHERE
过滤后的行按照分组键“装桶”,每个桶对应一组 - 若无
GROUP BY
,则把所有行看作一组
- 对
- HAVING
- 针对分组后结果进一步过滤,常用于限制聚合结果
- 可以使用聚合函数,也可以再次引用分组键
- 计算聚合函数
- SELECT
- 生成最终列
- 此时才产生列别名,因此别名可在
ORDER BY
使用,却不能在WHERE
使用
- ORDER BY
- 对上一阶段结果集排序
- 可用别名,列序号,或 SELECT 阶段产生别名
- 不在 SELECT 中出现但是唯一识别的列也可以进行排序
- LIMIT / OFFSET
- 截取
综合案例
SELECTe.workno,e.name,e.age,e.entrydate,DATE_FORMAT(NOW(), '%Y') - DATE_FORMAT(e.entrydate, '%Y') AS years_worked
FROM emp AS e
WHERE e.gender = 'M'AND e.age BETWEEN 20 AND 35
GROUP BY e.workno, e.name, e.age, e.entrydate -- 多列分组
HAVING COUNT(*) = 1 -- 每个工号仅保留唯一记录
ORDER BY e.age ASC, e.entrydate DESC
LIMIT 5;
- 写法 按图中顺序拼出来;
- 实际执行 则先
FROM
→WHERE
→GROUP BY
→HAVING
→SELECT
→ORDER BY
→LIMIT
。 - 由于
years_worked
在SELECT
阶段才计算,因此无法放在WHERE
,可以放HAVING
或ORDER BY
。
DCL
用户管理
创建用户,'demo_user'
是用户名,'%'
是主机名,当前是设置了所有主机都能够被访问,'123456'
是密码
create user 'demo_user'@'%' identified by '123456';
可以通过查询 user
表查看创建用户结果
Host | User | Select_priv | Insert_priv | Update_priv | Delete_priv | Create_priv | Drop_priv | Reload_priv | Shutdown_priv | Process_priv | File_priv | Grant_priv | References_priv | Index_priv | Alter_priv | Show_db_priv | Super_priv | Create_tmp_table_priv | Lock_tables_priv | Execute_priv | Repl_slave_priv | Repl_client_priv | Create_view_priv | Show_view_priv | Create_routine_priv | Alter_routine_priv | Create_user_priv | Event_priv | Trigger_priv | Create_tablespace_priv | ssl_type | ssl_cipher | x509_issuer | x509_subject | max_questions | max_updates | max_connections | max_user_connections | plugin | authentication_string | password_expired | password_last_changed | password_lifetime | account_locked | Create_role_priv | Drop_role_priv | Password_reuse_history | Password_reuse_time | Password_require_current | User_attributes |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
% | demo_user | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | 0 | 0 | 0 | 0 | caching_sha2_password | $A 005 005 005~W!;_u4_F,h6O}&xAtLnXWDO40tvxo.TjZ54ZDJ4Qe2jOg8M8XNswUaCz22 | N | 2025-06-05 21:18:23 | null | N | N | N | null | null | null | null |
此时如果需要更换密码,则需要
alter user 'demo_user'@'%' identified with mysql_native_password by '123457';
mysql 8.0 之后就不再支持
mysql_native_paasowrd
而是改用caching_sha2_password
如果需要删除用户,则需要
drop user 'demo_user'@'%';
权限控制
查询权限,正常用户的话,会输出以下信息,也就是不会给账户任何读写或者管理能力,只是声明了这个用户存在,但是对任何库,表以及视图没有操作权限
show grants for 'demo_user'@'%';
*.*
是 数据库对象的通配写法,它由两个星号组成,分别表示授权范围的“库层”和“表层”
grant all privileges on mysql_demo.* to 'demo_user'@'%';
约束 Constraint
概念:数据库层面由 DDL 定义的规则,用于限制一张表(或多张表)中列的取值范围与关系从而确保数据的一致性、正确性与完整性。当插入、更新、删除的数据不符合约束条件时,MySQL 会拒绝执行并返回错误,防止“脏数据”落库。
约束 ≠ 单纯的业务校验。约束由服务器强制执行,不依赖应用代码,能跨所有客户端生效。
约束 | 关键字 | 作用 | 典型意义 |
---|---|---|---|
非空 | NOT NULL | 禁止列值为 NULL | 必填字段,如 username |
唯一 | UNIQUE | 列(或列组)取值全局唯一 | 登录邮箱、手机号 |
主键 | PRIMARY KEY | 唯一 + 非空;自动创建聚簇索引 | 记录主身份标识 |
默认值 | DEFAULT expr | 未显式赋值时用默认表达式 | 状态默认 ‘N’ |
检查 | CHECK (expr) | 行级布尔表达式必须为 TRUE | age BETWEEN 0 AND 120 |
外键 | FOREIGN KEY … REFERENCES | 保证子表字段在父表存在,维护级联 | 订单-用户关联 |
自增 | AUTO_INCREMENT | 数值列自动 +1(InnoDB 主键或唯一索引) | 简单流水号 |
根据案例理解约束
需求如下
字段名 | 字段含义 | 字段类型 | 约束条件 | 约束关键字 |
---|---|---|---|---|
id | ID 唯一标识 | int | 主键,并且自动增长 | PRIMARY KEY, AUTO_INCREMENT |
name | 姓名 | varchar(10) | 不为空,并且唯一 | NOT NULL, UNIQUE |
age | 年龄 | int | 大于 0,并且小于等于 120 | CHECK |
status | 状态 | char(1) | 如果没有指定该值,默认为 1 | DEFAULT |
gender | 性别 | char(1) | 无 | — |
根据需求创建数据表
create table user (id int primary key auto_increment comment '主键',name varchar(20) not null unique comment '姓名',age int check ( age > 0 and age <= 120 ) comment '年龄',status char(1) default '1' comment '状态',gender char(1) comment '性别',
) comment '用户表';
测试数据
/* 1. 成功:所有字段符合约束 */
INSERT INTO user (name, age, status, gender)
VALUES ('Alice', 25, '1', 'F');/* 2. 失败:name 为 NULL,触发 NOT NULL 约束 */
INSERT INTO user (name, age, status, gender)
VALUES (NULL, 30, '1', 'M');/* 3. 成功:省略 status,验证 DEFAULT 自动填充为 '1' */
INSERT INTO user (name, age, gender)
VALUES ('Bob', 40, 'M');/* 4. 失败:name 与上一条重复,触发 UNIQUE 约束 */
INSERT INTO user (name, age, gender)
VALUES ('Bob', 35, 'M');/* 5. 失败:age = 0,触发 CHECK 下界 */
INSERT INTO user (name, age, gender)
VALUES ('Charlie', 0, 'M');/* 6. 失败:age = 121,触发 CHECK 上界 */
INSERT INTO user (name, age, gender)
VALUES ('Diana', 121, 'F');/* 7. 成功:边界值 age = 1(通过 CHECK) */
INSERT INTO user (name, age, gender)
VALUES ('Eve', 1, 'F');/* 8. 成功:边界值 age = 120(通过 CHECK) */
INSERT INTO user (name, age, gender)
VALUES ('Frank', 120, 'M');
外键
概念:用来让两张表的数据之间建立链接,从而保证数据的一致性和完整性
完整概念:指在子表(child table)的某一列或多列上定义的规则,用来 引用父表(parent table)主键或唯一键的取值。当对这两张表进行插入、更新、删除时,MySQL 会自动检查并维护两张表之间的引用完整性(Referential Integrity),从而避免出现“孤儿数据”
外键语法
在创建数据表的时候新增外键
/* 父表:用户 */
CREATE TABLE user
(id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL
) ENGINE = InnoDB COMMENT ='用户表';/* 子表:订单 */
CREATE TABLE orders
(id BIGINT PRIMARY KEY AUTO_INCREMENT,order_no CHAR(20) NOT NULL UNIQUE,user_id BIGINT,amount DECIMAL(10, 2) NOT NULL,status ENUM ('NEW','PAID','CLOSED') DEFAULT 'NEW',/* 外键定义:引用 user(id),删除用户时级联删除订单 */CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB COMMENT ='订单表';
创建完数据表后新增外键
alter table orders add constraint fk_orders_user foreign key (user_id) references user (id);
删除外键
alter table orders drop foreign key fk_orders_user;
外键约束
动作 | MySQL 行为细节 | 典型使用场景 | 需要注意 |
---|---|---|---|
NO ACTION | 检查时机:立即(与 RESTRICT 相同);若子表存在引用则直接报错,父表行不会被删/改。InnoDB 内部实现与 RESTRICT 一致,仅是语法保留。 | 安全默认,禁止误删/误改父表。适合订单-客户这类“父子”强依赖场景,要先手动清理子表才能改父表。 | MySQL 8.0 以后仍无区别,只写其中一种即可。 |
RESTRICT | 行为同上,只是名字不同;标准 SQL 把 NO ACTION 用于“延迟检查”,但 InnoDB 没有延迟功能,两者等价。 | 同上。 | —— |
CASCADE | 删除 / 更新父表行时,自动同步子表对应行:- ON DELETE CASCADE ⇒ 删除子表行。- ON UPDATE CASCADE ⇒ 把外键列改成最新值。 | 父表记录生命周期与子表一致,比如:用户删除后连同购物车、头像记录一并删除。 | 慎用:大量级联删除可能锁表或误删;建议加索引并在业务上确保符合预期。 |
SET NULL | 删除 / 更新父表行时,把子表外键列设为 NULL。列必须允许 NULL。 | 保留子表历史而解除关联,如删除员工后把 orders.employee_id 置空。 | 更新为 NULL 仅影响外键列,不会递归到更下级。 |
SET DEFAULT | 删除 / 更新父表行时,将子表外键列改为 列默认值。 | 实际很少用,因 InnoDB 不支持;只有 NDB、Tokudb 等少数引擎可用。 | 在 InnoDB 里若尝试创建会报 ERROR 1215 (HY000)。 |
在 MySQL InnoDB 中,如果 foreign key
什么都不写的的话,系统默认采用 NO ACTION / RESTRICT
,也就是“先检查,如果子表仍然有引用就就直接报错并阻止父表行被删除或更新”
按照上文测试数据进行测试,此时 foreign key
没有设置,那么就是 RESTRICT
,此时执行以下指令
delete from user where id = 1;
会爆出以下错误
[23000][1451] Cannot delete or update a parent row: a foreign key constraint fails (`mysql_demo`.`orders`, CONSTRAINT `fk_orders_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`))
将外键约束设置为 on delete cascade on update cascade
,此时再执行删除指令,删除父表行,子表对应行自动删除
将外键约束设置为 on delete set null on update set null
,此时再执行删除指令,删除父表行,子表对应行设置为 NULL
子查询
行子查询
-- 查询与 “张无忌” 的薪资及直属领导相同的员工信息select * from emp where (salary, managerid) = (select salary, managerid from emp where name = "张无忌") and name != '张无忌'
表子查询
-- 查询与 “鹿杖客”,“宋远桥” 的职位和薪资相同的员工信息select * from emp where (salary, job) in (select salary, job from emp where name = "鹿杖客" or name = "宋远桥") and name != '宋远桥' and name != '鹿杖客'
事物 Transaction
概念:一组要么 全部成功、要么 全部失败回滚 的操作单元。它把多条 INSERT / UPDATE / DELETE 等语句打包成一个整体,确保数据始终处于一致状态。
MySQL 事务由存储引擎实现。InnoDB 完整支持事务;
MySQL 事务是默认自动提交的,也就是,当执行一条 DML 语句,MySQL 会立即隐式提交事务。
查询事务状态
select @@autocommit;
设置事务提交方式,1为自动提交,0为手动提交,该设置只对当前会话有效
set @@autocommit = 1;
当设置为0时,在当前对话中,每次执行 DML 语句都要加上 commit
语句提交事务,如果发现语句错误还能执行 rollback
语句回滚事务
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
也可以单独对每一段 SQL 设置手动开启事务
start transaction | begin transaction
根据上述案例就可以改成
start transaction
select * from account where name = '张三';
update account set money = money - 1000 where name = '张三';
update account set money = money + 1000 where name = '李四';
commit;
四大特性 ACID
- 原子性 Atomicity:事务是不可分割的最小操作但愿,要么全部成功,要么全部失败
- 一致性 Consistency:事务完成时,必须使所有数据都保持一致状态
- 隔离性 Isolation:数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
- 持久性 Durability:事务一旦提交或回滚,它对数据库中的数据的改变就是永久的
特性 | 含义 | 在 InnoDB 中的实现要点 |
---|---|---|
A 原子性 (Atomicity) | 事务中的所有操作要么全部提交,要么全部回滚 | undo 日志(回滚日志) |
C 一致性 (Consistency) | 事务开始前后,数据必须满足所有约束和规则 | 崩溃恢复、外键/约束检查 |
I 隔离性 (Isolation) | 并发事务之间互不干扰,隔离级别可调 | MVCC + 锁机制 |
D 持久性 (Durability) | 一旦提交即永久保存 | redo 日志 + 双写缓冲 |
并发事务问题
脏读
一个事务 A 读到了另一个事务 B 尚未提交的数据。如果随后事务 B 发生了回滚,事务 A 就等于使用了从未真正存在的脏数据,导致业务逻辑和结果都不可信。
脏读 = 读到别人未提交的数据,是隔离级别最低时的典型并发问题
在实际生产中,保持 READ COMMITTED 或者默认 REPEATABLE READ 足以消除脏风险
第一个事务
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.000 sec)mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 2000 |
| ming | 2000 |
+------+-------+
2 rows in set (0.001 sec)
第二个事务
mysql> start transaction;
Query OK, 0 rows affected (0.000 sec)mysql> update account set money = money + 1000 where name = "xiao";
Query OK, 1 row affected (0.002 sec)
Rows matched: 1 Changed: 1 Warnings: 0
第一个事务
mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 3000 |
| ming | 2000 |
+------+-------+
2 rows in set (0.000 sec)
需要将事务隔离级别设置高于 read uncommitted
就能解决脏读问题
不可重复读
同一个事务在两次读取同一行记录时,前后读到的数据内容不一致,在第一次读取和第二次读取之间,另一事务已经提交了对该行的 UPDATE 或者 DELETE 操作
让当前事务“重复”读取时得到不同结果,破坏了业务对一致性的假设
第一个事务
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.001 sec)mysql> start transaction;
Query OK, 0 rows affected (0.000 sec)mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 2000 |
| ming | 2000 |
+------+-------+
2 rows in set (0.001 sec)
第二个事务
mysql> start transaction;
Query OK, 0 rows affected (0.000 sec)mysql> update account set money = money + 1000 where name = "xiao";
Query OK, 1 row affected (0.001 sec)
Rows matched: 1 Changed: 1 Warnings: 0mysql> commit;
Query OK, 0 rows affected (0.002 sec)
第一个事务
mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 2000 |
| ming | 2000 |
+------+-------+
2 rows in set (0.001 sec)mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 3000 |
| ming | 2000 |
+------+-------+
需要将事务隔离级别设置高于 read committed
就能解决脏读问题
幻读
同一个事务在两次执行相同的范围查询时,第二次看到了“凭空多出来的新数据”
在两次查询时,另一个事务已经提交了插入或删除,而这些新旧行符合原来的查询条件,于是第一次“没看见”的记录在第二次突然出现/笑死,像个幽灵一样
第一个对话
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.000 sec)mysql> select * from account where name = "hong";
Empty set (0.000 sec)
第二个对话
mysql> start transaction;
Query OK, 0 rows affected (0.000 sec)mysql> insert into account (name, money) values ('hong', 1000);
Query OK, 1 row affected (0.000 sec)mysql> commit;
Query OK, 0 rows affected (0.002 sec)
第一个对话
mysql> select * from account where name = "hong";
Empty set (0.001 sec)mysql> insert into account (name, money) values ("hong", 2000);
Query OK, 1 row affected (0.003 sec)
第二个对话
mysql> select * from account;
+------+-------+
| name | money |
+------+-------+
| xiao | 3000 |
| ming | 2000 |
| hong | 1000 |
+------+-------+
3 rows in set (0.000 sec)
设置级别为 repeatable read
,虽然能解决不可重复读的并发异常,但是会导致幻读的并发异常,解决幻读的并发异常需要将隔离级别设置为 serialization
。(如果将 name
设置 unique
唯一,幻读的并发异常更加明显)
对比
并发异常 | 变化点 | 典型触发 |
---|---|---|
脏读 | 读到 未提交 的行或行内容 | 另一事务尚未提交 |
不可重复读 | 同一行内容前后不一致 | 另一事务 已提交 修改该行 |
幻读 | 行集合 前后不一致(多/少行) | 另一事务 已提交 插入或删除 |
事务隔离级别
隔离级别 Isolation Level 决定了并发事务之间可以“看见”彼此未提交或者已提交变更的程度。这是 ACID 中隔离性 Isolation 的具体实现参数,> 直接影响脏读、不可重复读、幻读等并发异常是否会发生,以及系统的并发吞吐量。
级别(由低到高) | MySQL 语法 | 能否脏读 | 能否不可重复读 | 能否幻读 | 并发性能 |
---|---|---|---|---|---|
READ UNCOMMITTED | SET SESSION transaction_isolation=‘READ-UNCOMMITTED’; | 可能 | 可能 | 可能 | 最高 |
READ COMMITTED | ‘READ-COMMITTED’ | 无 | 可能 | 可能 | 较高 |
REPEATABLE READ (MySQL 默认) | ‘REPEATABLE-READ’ | 无 | 无 | 标准视角:可能InnoDB:通过间隙锁/Next-Key 锁可避免 | 中 |
SERIALIZABLE | ‘SERIALIZABLE’ | 无 | 无 | 无 | 最低 |
- 级别越高,异常越少,但锁持有时间越长、并发能力越低
- MySQL 8/InnoDB 默认
REPEATABLE READ
,配合 MVCC + 间隙锁,已能消除绝大多数线上幻读场景
查看隔离级别
select @@transaction_isolation;
设置事务隔离级别,session
是当前会话隔离级别,global
是全局隔离级别
set session|global transaction isolation level read committed;
参考
- MySQL学习笔记 | 智云知识