当前位置: 首页 > news >正文

数据库Day04

一、脏读、不可重复读、幻读 示例

先建一张测试表 test_table,插入基础数据:

CREATE TABLE test_table (id INT PRIMARY KEY,name VARCHAR(50),balance DECIMAL(10, 2)
);
INSERT INTO test_table (id, name, balance) VALUES (1, 'Alice', 1000.00);
1. 脏读(Dirty Read)

        场景:事务 T1 修改数据但未提交,事务 T2 读到未提交的 “脏数据”,之后 T1 回滚,导致 T2 数据无效。

-- 开启事务 T1(故意不提交,模拟回滚场景)
START TRANSACTION;
UPDATE test_table 
SET balance = balance - 100 
WHERE id = 1; -- Alice 余额改为 900,但不提交-- 新开窗口/事务 T2,查询数据(此时能读到 T1 未提交的修改,即脏读)
START TRANSACTION;
SELECT balance FROM test_table WHERE id = 1; -- 会读到 900(脏数据)-- 回到 T1 窗口,回滚事务
ROLLBACK;-- 再回到 T2 窗口,重新查询(数据变回 1000,验证脏读)
SELECT balance FROM test_table WHERE id = 1; -- 结果为 1000
2. 不可重复读(Non - repeatable Read)

        场景:事务 T1 内多次读同一数据,事务 T2 在 T1 未结束时修改并提交,导致 T1 两次读结果不同。

-- 事务 T1:先读数据,等待一会再读
START TRANSACTION;
SELECT balance FROM test_table WHERE id = 1; -- 第一次读:1000-- 新开窗口/事务 T2,修改并提交
START TRANSACTION;
UPDATE test_table 
SET balance = balance - 200 
WHERE id = 1; -- 改为 800
COMMIT;-- 回到 T1 窗口,再次读(结果变为 800,出现不可重复读)
SELECT balance FROM test_table WHERE id = 1; -- 第二次读:800
COMMIT;
3. 幻读(Phantom Read)

        场景:事务 T1 按条件查询数据,事务 T2 插入满足条件的新数据并提交,T1 再次查询时 “多出” 数据,像幻觉。

-- 事务 T1:先按条件查询,等待一会再查
START TRANSACTION;
SELECT * FROM test_table WHERE id > 0; -- 第一次查:只有 id=1 的数据-- 新开窗口/事务 T2,插入满足条件的数据并提交
START TRANSACTION;
INSERT INTO test_table (id, name, balance) VALUES (2, 'Bob', 1500.00);
COMMIT;-- 回到 T1 窗口,再次按条件查询(多出 id=2 的数据,出现幻读)
SELECT * FROM test_table WHERE id > 0; -- 第二次查:id=1、id=2
COMMIT;

二、MySQL 隔离级别 验证

MySQL 默认隔离级别是 Repeatable Read(可重复读),可通过 SET TRANSACTION 切换隔离级别,验证不同级别对 脏读 / 不可重复读 / 幻读 的影响。

1. 读未提交(Read Uncommitted)
-- 会话 1:设置隔离级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE test_table SET balance = 900 WHERE id = 1; -- 不提交-- 会话 2:查询(会读到脏数据,验证脏读)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM test_table WHERE id = 1; -- 结果:900(脏读)
2. 读已提交(Read Committed)
-- 会话 1:修改数据但不提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE test_table SET balance = 800 WHERE id = 1; -- 不提交-- 会话 2:查询(读不到未提交数据,解决脏读)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM test_table WHERE id = 1; -- 结果:1000(脏读被解决)-- 会话 1 提交后,会话 2 再次查询(出现不可重复读)
COMMIT;
SELECT balance FROM test_table WHERE id = 1; -- 结果:800(不可重复读)
3. 可重复读(Repeatable Read,MySQL 默认)
-- 会话 1:查询数据,不提交
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM test_table WHERE id = 1; -- 第一次读:1000-- 会话 2:修改并提交
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
UPDATE test_table SET balance = 700 WHERE id = 1;
COMMIT;-- 会话 1 再次查询(结果仍为 1000,解决不可重复读;但幻读需特殊验证)
SELECT balance FROM test_table WHERE id = 1; -- 第二次读:1000(可重复读生效)
COMMIT;
4. 可串行化(Serializable)

最严格隔离级别,会锁表 / 行,避免所有并发问题,但性能最低

-- 会话 1:查询时,表被隐式加锁
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
SELECT * FROM test_table; -- 执行后,表被锁定-- 会话 2:尝试插入数据(会阻塞,直到会话 1 提交/回滚)
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
START TRANSACTION;
INSERT INTO test_table (id, name, balance) VALUES (3, 'Charlie', 2000.00); 
-- 此处会卡住,等待会话 1 结束

三、事务日志(redo/undo log) 原理验证

        日志是 “隐式” 工作的,无法直接用 SQL 触发,但可通过 崩溃恢复测试 验证 redo log 作用,或模拟回滚看 undo log 效果。

1. redo log 验证(模拟 MySQL 崩溃恢复)

核心逻辑:数据先写内存 Buffer Pool,再异步刷盘;redo log 保证崩溃时,未刷盘的修改可通过日志恢复。

-- 1. 关闭 MySQL 双 1 安全模式(测试用,生产别关!)
SET GLOBAL innodb_flush_log_at_trx_commit = 0; -- redo log 先写内存,再定时刷盘-- 2. 插入数据(数据写入 Buffer Pool,redo log 写入内存缓冲)
START TRANSACTION;
INSERT INTO test_table (id, name, balance) VALUES (4, 'David', 3000.00);
COMMIT; -- redo log buffer 标记为“需要刷盘”,但未立即刷-- 3. 模拟 MySQL 崩溃(手动重启服务,或用命令杀进程)
-- 重启后,MySQL 自动检测 redo log,恢复未刷盘的插入操作
-- 重新连接后查询:SELECT * FROM test_table; -- id=4 的数据还在(redo log 恢复)
2. undo log 验证(回滚时的逻辑恢复)

核心逻辑:修改数据时,undo log 记录 “反向操作”,回滚时用它撤销修改。

START TRANSACTION;
UPDATE test_table SET balance = 600 WHERE id = 1; -- 产生 undo log(记录“回滚时改回 1000”)
ROLLBACK; -- 触发 undo log,balance 变回 1000
SELECT balance FROM test_table WHERE id = 1; -- 结果:1000(undo 生效)

四、总结

  • 脏读 / 不可重复读 / 幻读:用多事务并发操作,演示 “读未提交数据”“多次读结果不同”“读新增数据” 的现象。
  • 隔离级别:通过 SET TRANSACTION 切换级别,验证不同级别对并发问题的解决能力。
  • 事务日志:redo log 保障崩溃恢复,undo log 保障回滚,可通过 “模拟崩溃”“手动回滚” 间接验证。

 

五、MySQL优化

1. 索引优化

  • 作用:大幅提升查询速度,减少磁盘 I/O 操作
  • 关键要点
    • 优先在频繁作为查询条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)的字段上建立索引
    • 避免过度索引(索引会增加写入 / 更新成本,占用存储空间)
    • 联合索引遵循 "最左前缀原则",合理安排字段顺序
    • 定期使用EXPLAIN分析索引使用情况,删除无用索引
    • 对于长字符串,可考虑前缀索引减少索引体积

2. 集群和读写分离

  • 集群架构
    • 主从复制:一主多从,主库负责写入,从库负责读取
    • 分担单库压力,提高系统吞吐量和可用性
    • 实现数据备份和故障转移,增强系统健壮性
  • 读写分离
    • 写操作走主库,读操作走从库,分离读写压力
    • 需处理主从同步延迟问题,可通过业务设计规避
    • 适合读多写少的场景(如电商商品详情页、新闻网站)

3. 避免使用 SELECT *

  • 优化点
    • 只查询需要的字段,减少数据传输量和内存占用
    • 避免读取无用字段,降低磁盘 I/O 和网络传输开销
    • 当表结构变更时,可减少潜在的兼容性问题
    • 有助于利用覆盖索引,避免回表查询,提高查询效率

4. 分库分表

  • 分库
    • 按业务模块(如用户库、订单库)或数据量(如历史库、当前库)拆分
    • 降低单库数据量,减少数据库服务器资源竞争
  • 分表
    • 纵向分表:将大表按字段拆分(如基础信息表 + 详细信息表),减少宽表影响
    • 横向分表:按规则(如用户 ID 哈希、时间范围)拆分数据到多个表
    • 解决单表数据量过大导致的查询缓慢、索引失效等问题

5. 选用合适的数据类型

  • 原则:在满足业务需求的前提下,选择最小、最合适的类型
  • 示例
    • 整数用 INT 而非 BIGINT,小范围用 TINYINT/SMALLINT
    • 字符串长度固定用 CHAR,不固定用 VARCHAR(合理设置长度)
    • 日期时间用 DATE/DATETIME/TIMESTAMP 而非字符串
    • 存储金额可用 DECIMAL 而非 FLOAT/DOUBLE,避免精度问题
  • 好处:减少存储空间,提高查询效率,降低内存消耗

6. 编写更优的 SQL 语句

  • 优化方向
    • 避免嵌套子查询,可改为 JOIN 操作
    • 减少使用 OR,可改用 UNION(需保证索引有效)
    • 合理使用 LIMIT 分页,避免一次性查询大量数据
    • 避免在 WHERE 子句中对字段进行函数运算(会导致索引失效)
    • 控制 JOIN 表的数量,多表连接会增加查询复杂度和资源消耗
    • 使用批量插入(INSERT ... VALUES (...), (...))替代循环单条插入
http://www.dtcms.com/a/308283.html

相关文章:

  • 探索 Vue 3.6 新特性:Vapor Mode 与高性能 Web 应用开发
  • 【计算机网络】IP地址、子网掩码、网关、DNS、IPV6是什么含义?计算机中如何设置子网掩码与网关?
  • 大数据精准获客平台的破局之道:数据大集网的深度赋能
  • JavaEE初阶1.0
  • 【Unity】实现小地图
  • QT信号和槽怎么传输自己定义的数据结构
  • Redis哨兵模式搭建
  • 【普中STM32精灵开发攻略】--第 2 章 开发板功能及使用介绍
  • Qt C++实现KD树
  • BH1750模块
  • 上证50期权2400是什么意思?
  • 常见中间件漏洞
  • 腾讯云edge
  • 【SpringMVC】拦截器,实现小型登录验证
  • 对于前端工程化的理解
  • 仓库管理系统-9-前端之Main主要区域的新增表单
  • 用AI一键生成可交互知识图谱:Knowledge Graph Generator 让信息可视化触手可及
  • 星云能量传送特效技术详解
  • 智能文本抽取技术:精准识别、定位并提取出关键信息
  • 05-netty基础-ByteBuf数据结构
  • cuda编程笔记(11)--学习cuBLAS的简单使用
  • 机械学习--逻辑回归
  • React组件化的封装
  • 内核寄存器操作mcu进入低功耗模式
  • Java 17 新特性解析与代码示例
  • JavaScript函数性能优化秘籍:基于V8引擎优化
  • YOLO+Pyqt一键打包成exe(可视化,以v5为例)
  • tomcat隐藏400报错信息
  • Augment Code与Cursor功能对比分析
  • BR/EDR PHY帧结构及其具体内容