尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MySQL 进阶讲解(三):事务、索引优化、存储引擎与高阶特性

MySQL 进阶讲解(三):事务、索引优化、存储引擎与高阶特性 1. 引言在掌握了 MySQL 的基础增删改查之后进阶之路才刚刚开始。事务保证数据的一致性索引决定查询的速度存储引擎影响数据的存储方式与可靠性而高阶特性则让 MySQL 在复杂业务场景中游刃有余。本文作为 MySQL 进阶系列的第三篇将系统讲解事务、索引优化、存储引擎与高阶特性四大主题帮助你从「会用 MySQL」走向「用好 MySQL」。2. 事务数据一致性的基石2.1 什么是事务事务Transaction是一组不可分割的数据库操作单元要么全部成功要么全部失败回滚。经典的转账场景最能说明问题A 账户扣款 100 元、B 账户入账 100 元这两步必须作为一个整体执行任何一步失败都要撤销全部操作。2.2 ACID 四大特性事务的可靠性由 ACID 四大特性保证原子性Atomicity事务内的操作要么全部提交要么全部回滚不存在中间状态。一致性Consistency事务执行前后数据库的完整性约束不被破坏数据始终处于合法状态。隔离性Isolation多个事务并发执行时彼此互不干扰每个事务看到的数据视图是独立的。持久性Durability事务一旦提交其对数据库的修改就是永久性的即使系统崩溃也不会丢失。2.3 隔离级别与并发问题SQL 标准定义了四种隔离级别从低到高依次为隔离级别脏读不可重复读幻读读未提交READ UNCOMMITTED可能可能可能读已提交READ COMMITTED不可能可能可能可重复读REPEATABLE READ不可能不可能可能串行化SERIALIZABLE不可能不可能不可能MySQL InnoDB 默认使用可重复读隔离级别并通过 MVCC多版本并发控制与间隙锁Gap Lock在绝大多数场景下解决了幻读问题。下面通过两个并发会话Session A / Session B的完整操作序列直观演示 InnoDB 在可重复读隔离级别下如何借助 MVCC 避免不可重复读、借助间隙锁解决幻读。准备建表并插入初始数据-- 会话 A 与 B 共用同一张表CREATETABLEaccount(idINTPRIMARYKEY,balanceDECIMAL(10,2)NOTNULL)ENGINEInnoDB;INSERTINTOaccount(id,balance)VALUES(1,100.00),(2,200.00);场景一MVCC 避免不可重复读-- 会话 A STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- 读到 100.00生成当前读版本快照-- 会话 B STARTTRANSACTION;UPDATEaccountSETbalance150.00WHEREid1;-- 修改并提交COMMIT;-- 会话 A继续SELECTbalanceFROMaccountWHEREid1;-- 仍读到 100.00MVCC 快照读不受 B 提交影响COMMIT;说明可重复读下会话 A 的普通SELECT是快照读基于事务开始时生成的版本链读取。即使会话 B 已提交修改A 再次查询仍看到事务开始时的旧版本100.00从而避免了不可重复读。场景二间隙锁解决幻读-- 会话 A STARTTRANSACTION;-- 对 id 范围 (1, 3) 加间隙锁锁定该区间内不存在的记录SELECT*FROMaccountWHEREidBETWEEN1AND3FORUPDATE;-- 会话 B STARTTRANSACTION;-- 尝试插入 id 2 的新记录会被间隙锁阻塞INSERTINTOaccount(id,balance)VALUES(2,300.00);-- 阻塞等待...-- 会话 A继续COMMIT;-- 释放间隙锁-- 会话 B继续-- 阻塞解除插入成功COMMIT;说明会话 A 使用SELECT ... FOR UPDATE对id BETWEEN 1 AND 3加锁InnoDB 会在该区间加上间隙锁阻止其他事务向其中插入新记录。这样会话 A 在事务内多次查询同一范围时结果集不会凭空多出记录从而解决了幻读。2.4 事务的使用示例-- 开启事务STARTTRANSACTION;-- 执行操作UPDATEaccountSETbalancebalance-100WHEREid1;UPDATEaccountSETbalancebalance100WHEREid2;-- 提交事务COMMIT;-- 若出错则回滚-- ROLLBACK;3. 索引优化查询提速的关键3.1 索引的本质与分类索引是帮助 MySQL 高效获取数据的数据结构本质上是空间换时间。InnoDB 使用 B 树作为索引结构叶子节点存放完整数据行非叶子节点只存放索引键因此树的高度低、IO 次数少。常见的索引类型包括主键索引每张表只能有一个数据按主键有序排列。唯一索引索引列的值不允许重复允许 NULL。普通索引加速查询不限制值的唯一性。联合索引多个列组合建立的索引遵循最左前缀原则。全文索引用于全文检索适合大文本字段。3.2 最左前缀原则联合索引(a, b, c)实际会建立(a)、(a, b)、(a, b, c)三个索引。查询条件必须从最左列开始连续匹配才能命中索引-- 命中索引WHEREa1ANDb2ANDc3;WHEREa1ANDb2;WHEREa1;-- 无法命中索引WHEREb2ANDc3;WHEREc3;3.3 索引失效的常见场景即使建立了索引错误的写法也会让索引失效对索引列使用函数或计算WHERE YEAR(create_time) 2024隐式类型转换WHERE phone 13800138000phone 为 varchar 类型前导模糊查询WHERE name LIKE %张OR 连接非索引列WHERE a 1 OR b 2b 无索引联合索引不满足最左前缀3.4 索引优化实战建议3.5 索引优化实战案例下面用一个订单表orders完整演示从建表、造数据到用EXPLAIN分析慢查询、建立联合索引并对比执行计划的优化过程。第一步建表CREATETABLEorders(idBIGINTAUTO_INCREMENTPRIMARYKEY,user_idBIGINTNOTNULL,order_noVARCHAR(32)NOTNULL,statusTINYINTNOTNULLDEFAULT0,amountDECIMAL(10,2)NOTNULL,create_timeDATETIMENOTNULL,KEYidx_user_id(user_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4;第二步插入示例数据-- 插入 10 万条示例数据可用存储过程批量生成INSERTINTOorders(user_id,order_no,status,amount,create_time)SELECTFLOOR(RAND()*10000)1,CONCAT(NO,LPAD(n,10,0)),FLOOR(RAND()*5),ROUND(RAND()*1000,2),NOW()-INTERVALFLOOR(RAND()*365)DAYFROM(SELECTrownum:rownum1ASnFROMinformation_schema.columnsa,information_schema.columnsb,(SELECTrownum:0)rLIMIT100000)t;第三步用 EXPLAIN 分析慢查询业务上经常需要按「用户 状态 下单时间」查询订单先看未建联合索引时的执行计划EXPLAINSELECT*FROMordersWHEREuser_id100ANDstatus1ANDcreate_time2024-01-01;此时key为idx_user_idtype为refrows可能高达数千甚至上万——因为只用了user_id单列索引status和create_time仍需在回表后逐行过滤数据量大时性能堪忧。第四步建立联合索引ALTERTABLEordersADDINDEXidx_user_status_time(user_id,status,create_time);第五步对比优化后的执行计划EXPLAINSELECT*FROMordersWHEREuser_id100ANDstatus1ANDcreate_time2024-01-01;优化后key变为idx_user_status_timetype仍为ref但rows大幅下降可能从数万降到几十Extra不再出现Using where的二次过滤查询效率显著提升。性能提升说明扫描行数骤减联合索引让user_id、status、create_time三个条件在索引内一次定位回表次数从「全量候选行」降为「精准命中行」。减少回表与 IO候选行越少回表查询完整数据行的次数越少磁盘 IO 与内存开销同步下降。遵循最左前缀该联合索引同时覆盖了(user_id)、(user_id, status)、(user_id, status, create_time)三种查询组合一索引多用。优化后的查询语句与业务写法保持一致无需改动 SQL仅通过合理设计联合索引即可获得数量级的性能提升。为高频查询的 WHERE、ORDER BY、GROUP BY 列建立索引。索引列尽量选择区分度高的列避免重复值过多。控制单表索引数量一般不超过 5 个过多会拖慢写入。使用EXPLAIN分析执行计划关注type、key、rows字段。EXPLAINSELECT*FROMordersWHEREuser_id100ANDstatus1;4. 存储引擎选择合适的存储底座4.1 InnoDB 与 MyISAM 对比MySQL 5.5 之后默认存储引擎为 InnoDB它与 MyISAM 的核心差异如下特性InnoDBMyISAM事务支持支持不支持锁粒度行级锁表级锁外键支持不支持崩溃恢复支持不支持全文索引支持5.6支持适用场景高并发、事务型业务只读、报表类业务4.2 InnoDB 的存储结构InnoDB 采用聚簇索引组织数据主键索引的叶子节点直接存储整行数据二级索引的叶子节点存储主键值。因此主键查询只需一次索引查找即可拿到数据。二级索引查询需要先找到主键再回表查询完整数据行回表。覆盖索引可以避免回表即查询的列全部包含在索引中。4.3 如何选择存储引擎下面通过一个决策树帮助你在不同业务场景下快速选定合适的存储引擎是否否是否是是否是否开始评估业务需求是否需要事务、外键或行级锁InnoDB数据是否可容忍丢失是否以只读、统计查询为主MyISAMInnoDB是否要求写入极快且数据量巨大Archive是否仅需临时缓存、重启即丢MEMORYInnoDB判断依据说明InnoDB需要事务保证数据一致性、外键约束或行级锁并发控制时首选 InnoDB它是绝大多数业务表的默认选择。MyISAM纯只读、大量COUNT统计、全文检索且不关心崩溃恢复的场景可考虑 MyISAM其查询与压缩效率更高。MEMORY数据仅作临时缓存、重启后允许丢失、追求极快读写速度时使用如表结构临时表、会话级缓存。Archive日志类、写入极快、几乎不更新且可容忍数据丢失的归档场景压缩比高、占用空间小。需要事务、外键、行级锁 → 选择 InnoDB。纯只读、大量 COUNT 统计、全文检索 → 可考虑 MyISAM。内存临时表 → 使用 MEMORY 引擎。日志类、写入极快且可容忍丢失 → 可考虑 Archive 引擎。-- 查看当前支持的存储引擎SHOWENGINES;-- 查看表的存储引擎SHOWTABLESTATUSWHERENameorders;5. 高阶特性让 MySQL 更强大5.1 视图视图是虚拟表不存储实际数据本质是保存的 SQL 查询。它简化复杂查询、提供数据安全隔离CREATEVIEWv_user_ordersASSELECTu.name,o.order_no,o.amountFROMusers uJOINorders oONu.ido.user_idWHEREo.status1;5.2 存储过程与函数存储过程将一组 SQL 封装在服务端减少网络传输、复用业务逻辑DELIMITER//CREATEPROCEDUREsp_get_user(INuidINT)BEGINSELECT*FROMusersWHEREiduid;END//DELIMITER;CALLsp_get_user(100);5.3 触发器触发器在 INSERT、UPDATE、DELETE 操作前后自动执行常用于审计日志、数据校验CREATETRIGGERtrg_order_auditAFTERINSERTONordersFOR EACH ROWBEGININSERTINTOorder_log(order_id,action,log_time)VALUES(NEW.id,INSERT,NOW());END;5.4 窗口函数MySQL 8.0 引入了窗口函数让排名、累计、移动平均等分析场景变得简洁高效SELECTname,salary,RANK()OVER(ORDERBYsalaryDESC)ASrank_noFROMemployees;5.5 分区表分区表将大表按规则拆分为多个物理分区提升查询与维护效率CREATETABLEorders_part(idINT,order_dateDATE)PARTITIONBYRANGE(YEAR(order_date))(PARTITIONp2022VALUESLESS THAN(2023),PARTITIONp2023VALUESLESS THAN(2024),PARTITIONp2024VALUESLESS THAN(2025));6. 总结本文围绕 MySQL 进阶的四大核心主题展开事务通过 ACID 保证数据一致性索引优化是查询提速的关键手段存储引擎决定了数据的存储方式与适用场景高阶特性则提供了视图、存储过程、触发器、窗口函数与分区表等强大能力。掌握这些内容你就能在真实业务中做出更合理的设计与优化决策。在实际项目中建议结合EXPLAIN分析执行计划、合理设计索引、根据业务特性选择存储引擎并善用 MySQL 8.0 的新特性让数据库真正成为业务的坚实底座。
返回列表