数据库原理及其应用
MySQL安装---Linux系统
第一步:更新系统
bash
sudo apt update && sudo apt upgrade -y
第二步:安装 MySQL 8.0
bash
sudo apt install mysql-server -y
等待安装完成(约 1-3 分钟,取决于网速)。
第三步:启动并设置开机自启
bash
sudo systemctl start mysql
sudo systemctl enable mysql
第四步:验证安装
bash
# 查看版本
mysql --version
# 查看运行状态
sudo systemctl status mysql --no-pager
第五步:登录测试
bash
# 方式一:使用 sudo
sudo mysql
# 方式二:使用密码(需先设置 root 密码)
mysql -u root -p
出错
sudo mysql
-- 设置 root 密码
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '你的强密码';
-- 刷新权限
FLUSH PRIVILEGES;
-- 退出
exit
$ mysql -u root -p
Enter password:
成功登录后会显示:
sql
Welcome to the MySQL monitor...
mysql>
退出 MySQL:
sql
exit
现在 Navicat 用 SSH 隧道连接:
**SSH 标签页**:
- 主机:`192.168.88.129`
- 端口:`22`
- 用户名:`lkx`
- 密码:(你的 lkx 密码)
**常规标签页**:
- 主机:`127.0.0.1`
- 端口:`3306`
- 用户名:`navicat`
- 密码:`123456`
开发
数据库基础知识
表约束
作用:限制字段数据,确保数据完整性与一致性。
| 约束类型 | 关键字 | 说明 |
|---|---|---|
| 非空约束 | NOT NULL |
字段值不可为 NULL |
| 唯一约束 | UNIQUE |
字段值全局唯一(允许多个 NULL) |
| 主键约束 | PRIMARY KEY |
非空 + 唯一,每表仅一个,常配合 AUTO_INCREMENT |
| 默认约束 | DEFAULT |
插入时若未指定值,则使用默认值 |
| 外键约束 | FOREIGN KEY |
引用另一表的主键,维护表间引用完整性 |
| 检查约束 | CHECK (expr) |
限定字段取值范围(如 age >= 0),注:MySQL 8.0.16+ 才完全支持 |
| 自增 | AUTO_INCREMENT |
整型字段自动递增,通常用于主键 |
外键约束的更新/删除行为
定义外键时可指定关联表记录被更新或删除时的动作:
| 行为 | 说明 |
|---|---|
NO ACTION |
默认行为,若子表存在关联记录,则拒绝操作(等同于 RESTRICT) |
CASCADE |
级联操作:主表记录更新/删除 → 子表关联记录同步更新/删除 |
SET NULL |
主表记录被删/改 → 子表外键字段设为 NULL(要求该字段允许 NULL) |
语法示例:
sql
FOREIGN KEY (dept_id) REFERENCES departments(id)
ON UPDATE CASCADE
ON DELETE SET NULL;
多表连接查询
笛卡尔积(Cartesian Product)
- 无连接条件时,返回两表所有行的组合(
m × n行),通常需避免。
内连接(Inner Join)
- 仅返回两表匹配的记录。
sql
SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.t1_id;
-- INNER 可省略
外连接(Outer Join)
- 左外连接(LEFT JOIN) :返回左表全部 + 右表匹配部分(无匹配则右表字段为
NULL) - 右外连接(RIGHT JOIN):返回右表全部 + 左表匹配部分
自连接(Self Join)
- 同一张表"自己连自己",需使用别名区分。
sql
SELECT e.name, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;
联合查询
合并多个 SELECT 结果集:
| 操作符 | 特点 |
|---|---|
UNION ALL |
保留所有行(含重复) |
UNION |
自动去重(性能略低) |
要求:
各查询列数相同、对应列类型兼容、列名以第一个查询为准
子查询
在 SELECT / WHERE / FROM 中嵌套 SELECT。
1.标量子查询
返回单行单列(一个值)
sql
SELECT name FROM users WHERE age > (SELECT AVG(age) FROM users);
2.列子查询
返回单列多行
常配合 IN / NOT IN / ANY / SOME / ALL
sql
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE active = 1);
3.行子查询
返回一行多列
sql
SELECT * FROM orders
WHERE (customer_id, order_date) = (SELECT id, MAX(created_at) FROM customers);
4.表子查询(派生表)
返回多行多列,作为临时表使用(需起别名)
sql
SELECT t.avg_price, t.category
FROM (SELECT AVG(price) AS avg_price, category FROM products GROUP BY category) AS t
WHERE t.avg_price > 100;
数据库进阶知识
事务
事务的基本操作命令
-
查看当前会话的自动提交状态:
mysqlSELECT @@autocommit;- 返回
1表示开启自动提交(每条 DML 语句独立成事务)。 - 返回
0表示关闭自动提交(需手动控制事务边界)。
- 返回
-
关闭自动提交(进入手动事务模式):
mysqlSET @@autocommit = 0; -
显式开启一个新事务(两种等效写法):
mysqlSTART TRANSACTION; -- 或 BEGIN; -
提交事务(使修改永久生效):
mysqlCOMMIT; -
回滚事务(撤销未提交的所有修改):
mysqlROLLBACK;
注意:
- DDL 语句(如
CREATE,ALTER,DROP)会隐式提交当前事务。- 在自动提交模式下,每条
INSERT/UPDATE/DELETE语句执行后立即提交,无法回滚。
事务的四大特性(ACID)
| 特性 | 说明 |
|---|---|
| 原子性(Atomicity) | 事务是一个不可分割的工作单元,其中的操作要么全部成功,要么全部失败回滚。 |
| 一致性(Consistency) | 事务执行前后,数据库必须从一个一致状态转移到另一个一致状态(如转账前后总金额不变)。 |
| 隔离性(Isolation) | 并发执行的多个事务相互隔离,互不干扰。具体隔离程度由隔离级别决定。 |
| 持久性(Durability) | 一旦事务提交,其对数据的修改将永久保存到存储介质中,即使系统崩溃也不会丢失。 |
事务隔离级别
MySQL 支持四种标准 SQL 隔离级别,通过以下命令查看和设置:
-
查看当前会话的隔离级别:
sqlSELECT @@transaction_isolation; -
设置当前会话的隔离级别(仅对当前连接生效):
sqlSET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
四种隔离级别对比
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | 允许 | 允许 | 允许 | 性能最高,一致性最弱,一般不用于生产。 |
| READ COMMITTED | 禁止 | 允许 | 允许 | 每次读取都获取最新已提交数据(Oracle 默认)。 |
| REPEATABLE READ | 禁止 | 禁止 | InnoDB 通过 MVCC + 间隙锁避免幻读 | MySQL InnoDB 默认隔离级别,保证同一事务内多次读结果一致。 |
| SERIALIZABLE | 禁止 | 禁止 | 禁止 | 最高隔离,完全串行执行,性能最低。 |
说明:
- 脏读:读到其他事务未提交的数据。
- 不可重复读:同一事务内,两次读同一行数据结果不同(因其他事务已提交修改)。
- 幻读:同一事务内,两次查询返回的行数不同(因其他事务插入/删除了满足条件的行)。
- InnoDB 在 REPEATABLE READ 级别下,通过 Next-Key Lock(行锁 + 间隙锁) 解决幻读问题。
存储引擎
基本概念
- 存储引擎是 MySQL 中用于管理表数据存储与检索的底层组件。
- 引擎作用于表级别,而非数据库级别。同一数据库中的不同表可使用不同存储引擎。
- 创建表时可通过
ENGINE = 引擎名指定存储引擎,默认通常为 InnoDB。
查看与设置命令
mysql
-- 查看当前 MySQL 支持的所有存储引擎及其状态
SHOW ENGINES;
-- 查看某张表的建表语句,包含所用引擎
SHOW CREATE TABLE 表名;
-- 创建表时显式指定引擎
CREATE TABLE 表名 (
列定义...
) ENGINE = InnoDB;
主要存储引擎对比
| 特性 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| 事务支持 | 支持(ACID) | 不支持 | 不支持 |
| 外键约束 | 支持 | 不支持 | 不支持 |
| 锁机制 | 行级锁(高并发性能好) | 表级锁(并发写入性能差) | 行级锁(数据在内存) |
| 崩溃恢复能力 | 支持自动恢复 | 无自动恢复,可能丢失数据 | 无持久性,重启后数据丢失 |
| 数据持久性 | 是 | 是 | 否 |
| 典型文件 | .ibd(含数据和索引) | .MYD(数据)、.MYI(索引) | 仅 .sdi(结构),数据在内存 |
| 适用场景 | 用户系统、订单、支付等核心业务 | 只读或读多写少的静态数据 | 临时中间结果、会话缓存 |
文件结构说明
- InnoDB
.ibd文件:包含表的数据和索引(MySQL 5.6+ 默认开启innodb_file_per_table)。- 表结构元数据存储在数据字典中(MySQL 8.0 起也以
.sdi文件形式存在)。
- MyISAM
.sdi:表结构定义.MYD:表数据(MYData).MYI:索引数据(MYIndex)
- Memory
- 仅在内存中存储数据,
.sdi文件保存结构定义,服务重启后数据清空。
- 仅在内存中存储数据,
InnoDB 逻辑存储结构
InnoDB 的数据组织层级从上到下为:
- Tablespace(表空间):如系统表空间或独立表空间(.ibd 文件)。
- Segment(段):分为数据段、索引段等。
- Extent(区):连续的 1MB 空间,由 64 个 Page 组成。
- Page(页):最小 I/O 单位,默认 16KB。
- Row(行):实际存储的记录。
该结构决定了即使查询单行数据,也可能触发整页(16KB)的磁盘读取。
MySQL 架构分层
MySQL 服务器逻辑上分为四层:
-
连接层:处理客户端连接、身份认证、安全校验。
-
服务层:SQL 解析、优化、缓存、内置函数执行(如日期、字符串函数)。
-
引擎层:插件式存储引擎接口,InnoDB、MyISAM 等在此实现。
-
存储层:物理文件系统,负责数据落盘或内存存储。
索引
索引的基本概念
- 索引是帮助 MySQL 高效获取数据的有序数据结构。
- 类比:书籍的目录,通过目录可快速定位内容,而无需逐页翻阅。
索引的优缺点
| 优点 | 缺点 |
|---|---|
- 显著提高 SELECT 查询效率- 减少排序和分组(ORDER BY, GROUP BY)的 CPU 开销- 支持快速连接(JOIN) |
- 占用额外磁盘空间- 降低 INSERT、UPDATE、DELETE 操作性能(需同步维护索引)- 增加 ALTER TABLE(如加列)的执行时间 |
常见索引结构
(1)B+Tree 索引(主流)
- InnoDB 和 MyISAM 默认使用 B+Tree。
- 特点:
-
所有数据记录都存储在叶子节点,非叶子节点仅存索引键和指针。
-
叶子节点之间通过双向链表连接 ,支持高效范围查询(如
WHERE id BETWEEN 10 AND 100)。 -
树高度低(通常 3~4 层),I/O 次数少,适合磁盘存储。
-
(2)Hash 索引
- 仅 Memory 引擎原生支持;InnoDB 提供自适应哈希索引(内部优化,不可显式创建)。
- 特点:
- 基于哈希表实现,仅支持等值查询 (如
WHERE id = 100)。 - 不支持范围查询、排序、最左前缀匹配。
- 查找时间复杂度为 O(1),但哈希冲突会降低性能。
- 基于哈希表实现,仅支持等值查询 (如
为什么 InnoDB 选择 B+Tree 而非其他结构
| 对比项 | 二叉搜索树 | B-Tree | B+Tree(InnoDB 选择) |
|---|---|---|---|
| 层级深度 | 深(O(log₂N)),I/O 多 | 较浅(多路平衡) | 更浅(扇出更大),I/O 更少 |
| 范围查询 | 不支持高效遍历 | 可支持,但需中序遍历 | 叶子节点链表,高效范围扫描 |
| 数据存储位置 | 键与数据混存 | 键与数据可存于非叶节点 | 数据仅存于叶子节点,非叶节点更紧凑,单页可存更多键,树更矮 |
| 磁盘友好性 | 差 | 较好 | 最优(顺序 I/O + 高扇出) |
关键结论: B+Tree 在磁盘 I/O 效率、范围查询支持、内存利用率三方面综合最优,特别适合数据库的读多写少、范围扫描频繁的场景。
索引类型
按逻辑功能分类
| 类型 | 说明 |
|---|---|
| 主键索引(PRIMARY KEY) | 唯一、非空,自动聚簇(InnoDB 中数据按主键物理排序)。 |
| 唯一索引(UNIQUE) | 值唯一(允许一个 NULL),用于保证字段唯一性。 |
| 普通索引(INDEX / KEY) | 最基本的索引,无唯一性约束。 |
| 组合索引(复合索引) | 多列组成的索引,遵循最左前缀原则。 |
| 全文索引(FULLTEXT) | 用于文本关键词搜索、用于 MATCH ... AGAINST 文本搜索,适用于大文本字段。 |
InnoDB 特有:按存储结构分类
-
1.聚簇索引
- 叶子节点直接存储整行数据 。 主键即聚簇索引。若未定义主键,InnoDB 会隐式生成 6 字节 row_id 作为聚簇索引。
-
2.二级索引
- 叶子节点存储主键值,而非整行数据。 查询时需 回表:先查二级索引得主键,再用主键查聚簇索引获取完整行。
sqlSELECT * FROM user WHERE id = 10; -- 直接走聚簇索引,一次 I/O SELECT * FROM user WHERE name = 'Arm'; -- 走 name 的二级索引,需回表,两次 I/O结论:第一条效率更高。
B+Tree 高度估算(InnoDB)
-
InnoDB 页大小默认为 16KB(16384 字节)。
-
假设一行数据大小为 1KB ,则一页可存约 16 行。
-
主键为
BIGINT(8 字节),指针占 6 字节(页号)。 -
对于非叶子节点(仅存键和指针),每个节点最多容纳键值数
n满足:n * 8 + (n + 1) * 6 ≤ 16384 => 14n + 6 ≤ 16384 => n ≈ 1170 -
叶子节点每页存 16 行,总记录数 N:
- 高度为 2:最多支持
1170 * 16 ≈ 18,720行 - 高度为 3:
1170 * 1170 * 16 ≈ 2190 万行 - 高度为 4:超 250 亿 行
- 高度为 2:最多支持
结论:绝大多数业务表 B+Tree 高度为 3,查询只需 3 次磁盘 I/O。
索引语法
sql
-- 创建索引
CREATE [UNIQUE] INDEX index_name ON table_name (column_list);
-- 查看表的所有索引
SHOW INDEX FROM table_name;
-- 删除索引
DROP INDEX index_name ON table_name;
示例:为
name字段创建普通索引
sqlCREATE INDEX idx_name ON user(name); SHOW INDEX FROM user;
SQL 性能分析
查看 SQL 执行频率
sql
SHOW GLOBAL STATUS LIKE 'Com_%';
-
Com_select、Com_insert等表示各类语句执行次数。
慢查询日志
-
默认关闭,需在配置文件(如
my.cnf)中开启:inislow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 -- 超过 2 秒的查询记为慢查询 log_queries_not_using_indexes = 1 -- 可选:记录未用索引的查询 -
开启后,定期分析慢日志,定位性能瓶颈。
使用 SHOW PROFILES
sql
SELECT @@have_profiling; -- 查看是否支持
SET profiling = 1; -- 开启
-- 执行 SQL
SHOW PROFILES; -- 列出所有查询 ID 和耗时
SHOW PROFILE FOR QUERY 1; -- 查看 ID=1 的详细阶段耗时
EXPLAIN 执行计划
sql
EXPLAIN SELECT * FROM user WHERE name = 'Arm';
关键字段说明:
| 字段 | 说明 |
|---|---|
| id | 查询序号。相同值按从上到下执行;值越大越先执行(子查询场景)。 |
| select_type | 查询类型(SIMPLE、PRIMARY、SUBQUERY 等)。 |
| type | 访问类型(最重要) ,性能从优到劣:NULL > system > const > eq_ref > ref > range > index > ALL- const/eq_ref:主键或唯一索引等值查询- ref:普通索引等值查询- range:范围查询(如 BETWEEN, IN)- ALL:全表扫描(需优化) |
| possible_keys | 可能使用的索引。 |
| key | 实际使用的索引。若为 NULL,表示未命中索引。 |
| key_len | 使用索引的字节数,可判断是否用到组合索引的全部列。 |
| rows | 预估扫描行数,越小越好。 |
| filtered | 百分比,表示存储引擎返回的数据经 Server 层过滤后剩余比例。 |
| Extra | 附加信息,重点关注:- Using index:覆盖索引(无需回表)- Using where:Server 层过滤- Using filesort:需额外排序(性能差)- Using temporary:使用临时表(性能差) |
索引使用
索引效率验证
-
执行原始查询(未建索引)
sqlSELECT * FROM table_name WHERE column_name = 'value';- 观察执行时间或通过
EXPLAIN确认是否发生全表扫描(type = ALL)。
- 观察执行时间或通过
-
创建索引
sqlCREATE INDEX idx_column ON table_name (column_name); -
再次执行相同查询并分析执行计划
sqlEXPLAIN SELECT * FROM table_name WHERE column_name = 'value';- 若
key字段显示所建索引名,且type为ref、const或range,说明索引生效。 - 实际执行时间应显著降低(尤其在数据量较大时)。
- 若
索引失效的常见场景
- 最左前缀法则(联合索引)
-
对于联合索引
(col1, col2, col3),查询条件必须从最左侧列开始,且不能跳过中间列。 -
有效用法:
sqlWHERE col1 = ? WHERE col1 = ? AND col2 = ? WHERE col1 = ? AND col2 = ? AND col3 = ? -
失效用法:
sqlWHERE col2 = ? -- 跳过 col1 WHERE col1 = ? AND col3 = ? -- 跳过 col2
- 范围查询的影响
-
在联合索引中,范围条件(
>,<,BETWEEN,!=等)右侧的列无法使用索引。 -
示例(索引:
(col_a, col_b)):sqlWHERE col_a = 1 AND col_b > 100 -- col_a 等值,col_b 范围,索引可用 WHERE col_a > 1 AND col_b = 100 -- col_a 范围,col_b 索引失效
3.索引列参与运算或函数
-
在索引列上使用函数、表达式或计算会导致索引失效。
-
错误示例:
sqlWHERE YEAR(date_column) = 2025 WHERE price + tax > 100 -
正确改写:
sql
WHERE date_column >= '2025-01-01' AND date_column < '2026-01-01'
WHERE price > 100 - tax -- 若 tax 为常量
- 字符串值未加单引号(隐式类型转换)
-
当索引列为字符串类型时,查询值必须用单引号包裹。
-
错误:
sqlWHERE varchar_column = 12345 -- 触发隐式转换,索引失效 -
正确:
sql
WHERE varchar_column = '12345'
- 模糊查询(LIKE)规则
-
前缀匹配(尾部模糊)可使用索引 :
sqlWHERE text_column LIKE 'prefix%' -- 可用索引 -
非前缀匹配(含头部通配符)索引失效 :
sqlWHERE text_column LIKE '%suffix' -- 全表扫描 WHERE text_column LIKE '%contain%' -- 全表扫描
- OR 条件的索引使用
-
若
OR连接的多个条件中,任一列无索引,则整个 OR 子句可能无法使用索引。 -
风险示例(仅
col_x有索引):sqlWHERE col_x = 'A' OR col_y = 'B' -- col_y 无索引,可能导致全表扫描 -
优化方案:
-
为所有 OR 涉及列建立索引;
-
或改写为
UNION:sqlSELECT * FROM table_name WHERE col_x = 'A' UNION SELECT * FROM table_name WHERE col_y = 'B';
-
- 数据分布影响优化器决策
-
即使存在索引,若 MySQL 估算使用索引的 I/O 成本高于全表扫描(如高选择性低的字段),则会放弃索引。
-
典型场景:性别、状态等低区分度字段。
8.SQL 索引提示
用于显式控制优化器对索引的选择:
sql
-- 建议使用指定索引(优化器可忽略)
SELECT * FROM table_name USE INDEX (index_name) WHERE ...;
-- 忽略指定索引
SELECT * FROM table_name IGNORE INDEX (index_name) WHERE ...;
-- 强制使用指定索引(若不可用则报错)
SELECT * FROM table_name FORCE INDEX (index_name) WHERE ...;
9.覆盖索引
- 定义:查询所需的所有列均包含在索引中,无需回表查询聚簇索引。
- 优势 :
- 避免回表(减少 I/O 操作);
- 提升查询性能,尤其在高并发场景;
EXPLAIN中Extra字段显示Using index表示命中覆盖索引。
- 使用建议 :
-
避免
SELECT *,明确指定所需字段; -
若查询字段均为索引列,优先构建包含这些字段的联合索引。
-
10.前缀索引
-
适用场景 :对长字符串列(如
VARCHAR(255))建立完整索引成本高。 -
方法 :仅对字符串的前 N 个字符建立索引。
sqlCREATE INDEX idx_prefix ON table_name (column_name(N)); -
注意事项 :
- 前缀长度需保证足够高的区分度 (可通过
SELECT COUNT(DISTINCT LEFT(column_name, N)) / COUNT(*)估算); - 前缀索引不支持覆盖索引(因索引中不含完整列值);
- 无法用于
ORDER BY或GROUP BY的完整排序(除非前缀足以区分)。
- 前缀长度需保证足够高的区分度 (可通过
11.单列索引 vs 联合索引的选择
| 场景 | 推荐策略 |
|---|---|
| 仅单一条件高频查询 | 单列索引即可 |
多个字段组合查询(如 WHERE a = ? AND b = ?) |
优先使用联合索引 (a, b) |
同时存在 WHERE a 和 WHERE a AND b 查询 |
联合索引 (a, b) 可同时满足 |
多个独立高频查询(如 WHERE a 和 WHERE b 互不相关) |
可分别建单列索引,或评估是否用联合索引覆盖 |
索引设计通用原则
-
针对大数据量且查询频繁的表建索引
- 小表(如配置表 < 1000 行)通常无需索引。
-
索引应覆盖常见查询模式
- 包括
WHERE条件、ORDER BY、GROUP BY、JOIN关联字段。
- 包括
-
优先考虑高区分度字段
- 区分度 =
COUNT(DISTINCT column) / COUNT(*),越接近 1 越适合建索引; - 唯一索引(如用户 ID、订单号)是理想选择。
- 区分度 =
-
控制索引数量
- 每增加一个索引,会降低写操作(
INSERT/UPDATE/DELETE)性能; - 避免重复或冗余索引(如
(a)与(a, b)共存时,(a)可能冗余)。
- 每增加一个索引,会降低写操作(
-
优先使用联合索引,合理应用前缀索引
- 联合索引可减少索引数量,提升复合查询效率;
- 对长字符串,使用前缀索引平衡空间与性能。
-
避免过度索引
- 不为低频查询、低区分度字段(如性别、状态)建索引;
- 定期审查并删除未使用的索引(可通过
sys.schema_unused_indexes或慢日志分析)。