MySQL(一)数据库风险操作场景总结

MySQL 数据库风险操作场景总结

1. 索引失效

MYSQL中索引数据是按照B+树结构组织的,而B+树本质上是多叉查找树,每个节点是按照索引值大小有序排列存储的(二分查找必要条件),索引的查找匹配过程也是由根节点向叶子节点逐层比较,所以使用索引最基本的要求就是能保证索引值有序,否则就会出现索引失效的场景。

失效场景 示例 失效原因 如何解决
对索引使用左或者左右模糊匹配 订单表order_info中订单号字段为VARCHAR类型且存在二级索引SELECT * FROM order_info WHERE order_id LIKE '%2026' 字符串类型索引值排序规则由两方面决定:整体比较规则:按照字典序顺序比较每个字符字符比较规则:字符的大小由字段的校对规则决定,比如常用的utf8_general_ci含义为字符集使用UTF-8、不区分大小写等当使用左或者左右模糊匹配时会导致无法确定字典序的前缀,从而无法执行B+树的搜索算法,从而走全表扫描。 避免使用左或者左右模糊匹配
对索引使用函数 订单表order_info中订单号字段为VARCHAR类型且存在二级索引SELECT * FROM order_info WHERE length(order_id)=10 索引结构保存的是索引字段的原始值,而不是经函数计算后的值 避免使用函数或建立函数索引(MYSQL8.0+)
对索引进行表达式计算 订单表order_info中截止时间字段为BIGINT类型且存在二级索引SELECT * FROM order_info WHERE end_time-86400>=1764518400 索引结构保存的是索引字段的原始值,而不是经表达式计算后的值 避免进行表达式计算或将除索引外的计算项转换到表达式右侧end_time>=1764518400+86400
对索引隐式类型转换 订单表order_info中订单号字段为VARCHAR类型且存在二级索引SELECT * FROM order_info WHERE order_id = 202601011 当操作符与不同类型的操作数同时使用时,默认会发生隐式类型转换以使操作数兼容。以字符串和数字为例,MYSQL会自动**将字符类型转换为数字,**转换规则是自左至右扫描字符串,直到遇到非数字字符停止。因此,如果索引列为字符类型且发生隐式类型转换,相当于对索引使用函数,导致索引失效。 避免出现隐式类型转换,优化表结构、统一数据类型
联合索引非最左匹配 订单表order_info中存在联合索引(customer_id,order_type,order_status)``SELECT * FROM order_info WHERE order_type=1 AND order_status=1 联合索引数据按照最左优先的方式进行索引的匹配,即首先按照第一个索引排序,若第一个索引数据相同时才会按照第二个索引排序,因此后续索引的有序性是在左边索引的基础上建立的,不遵循最左匹配原则会导致联合索引失效 联合索引遵循最左匹配原则,需要注意的是联合索引最左匹配原则在范围查询时会停止匹配 ,也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引(因为后续的索引值在前面索引筛选后是无序的),但是可以使用索引下推优化
OR条件列存在非索引 订单表order_info中截止时间字段end_time存在二级索引,逻辑删除标识deleted为普通字段SELECT * FROM order_info WHERE end_time<=1748707200 OR deleted=1 OR条件需要合并结果集,若其中一个条件无索引则会进行全表扫描 避免用OR连接条件或确保所有OR条件都有索引

2. 唯一索引限制

2.1 唯一索引包含NULL

唯一索引字段允许多个NULL值,插入重复NULL值不会违反唯一约束,因此需要特别注意:创建唯一索引的字段不允许出现NULL,否则数据库的唯一性约束可能会失效,导致唯一索引出现重复数据。

2.2 逻辑删除与唯一索引冲突

目前大部分业务系统的数据库设计都不会真正的物理删除数据,而是通过添加逻辑删除标识比如deleted=1来表示,通过这种方式删除数据之后数据仍然还在表中,只是从逻辑上过滤了删除状态的数据而已,此时若业务允许重新添加相同唯一索引值的数据则会导致失败。该问题可以通过以下方式解决:

  • 逻辑删除标识递增: 将原唯一索引与逻辑删除标识作为联合唯一索引,未删除状态下默认为0,在每次删除操作后使得逻辑删除标识递增+1;
  • 增加删除时间戳: 将原唯一索引、逻辑删除标识、时间戳作为联合唯一索引,若有逻辑删除操作则自动往该字段写入时间戳;
  • 增加删除ID字段: 将原唯一索引、逻辑删除标识、删除ID字段作为联合唯一索引,该字段用于在删除操作时记录当前记录的主键ID,该方式无需修改已有删除逻辑、也能保证数据的唯一性且高并发也不会出现数据重复;

2.3 历史数据添加唯一索引

在给历史表添加唯一索引时,需要注意评估要添加唯一索引的字段列是否存在重复数据,若存在则需要先对重复历史数据进行处理后再添加唯一索引,否则加索引会失败。

3. 死锁问题

死锁大部分的根本原因是有两个或多个事务之间加锁顺序的不一致导致的,这其实是最经典的死锁场景。常见的死锁场景分析推荐阅读如下文章(图源引用):

4. DDL 操作

DDL (Data Definition Language) 是数据定义语言,用于定义或修改数据库结构,操作对象包括数据库、表、列、索引等结构本身,相关的命令有:CREATEALTERDROP 等。DML (Data Manipulation Language)是数据操作语言,用于操作数据库中具体的数据行,相关的命令有:SELECTINSERTUPDATEDELETE 等。线上业务系统执行DDL操作需要谨慎评估,因为涉及到表结构修改和业务阻塞风险,常见的DDL执行方案包括:

  1. MYSQL原生OnlineDDL: Online DDL是指在执行DDL变更操作时,允许数据库继续并发处理DML操作,而不需要长时间锁定表或使表不可用的特性。MYSQL在5.6版本之后开始正式支持Online DDL特性,发展到8.0版本经历了多次调整和完善,能够支持更多类型的DDL操作实现 Online,并且 ALGORITHM 新增了 INSTANT 算法支持,官方文档参考 MYSQL8.0 Online DDL Operations
  2. 第三方工具: 有很多成熟的第三方工具支持辅助在线完成DDL变更操作,比如 Percona Toolkit 工具集下的 pt-online-schema-change,其基本原理是创建原表结构变更后的影子表,然后将原始表的数据逐步拷贝到影子表中,同时通过触发器机制实时捕获并应用变更(不阻塞并发DML),最终替换掉原表,该方式需要提前预留好新表空间且整个过程较慢,官方文档参考 pt-online-schema-change
  3. 主从切换: 在从库上执行DDL操作变更,完成后执行主从切换将从库切换为主库,该方法相比前两种方式更重但更加通用,可以作为评估后的兜底方式;

本节将重点分析 Online DDL 的使用场景,但是注意并不是所有的DDL操作都支持 Online DDL,具体说明可以参考官方文档:MYSQL8.0 Online DDL Operations。常见的Online DDL包括创建索引、删除索引、添加字段列等,而不支持Online DDL的则包括修改字段列数据类型、修改字符集等。DDL操作的执行算法可以由参数ALGORITHM [=] {DEFAULT | INSTANT | INPLACE | COPY}指定,其含义如下:

  • DEFAULT:默认执行算法,由数据库根据操作类型自动选择最优方式;
  • COPY: 拷贝原始表的副本,对副本执行操作,并将表数据逐行从原始表复制到新表,期间不允许并发DML。因此仅支持COPY方式的DDL操作是非Online的;
  • INPLACE: 无需拷贝新表,直接在原始表的存储文件上进行操作,但是若涉及行记录格式变更也需要重建表(rebuild-table),整个过程仅在引擎层完成,并通过ROW_LOG来缓存期间的DML增量操作,该方式可以支持Online DDL;
  • INSTANTMySQL 8.0.12中引入的新算法,该方式仅需修改数据库中的元数据,表数据不受影响且操作瞬间完成,该方式可以支持Online DDL;

DDL操作中比较重要的锁是元数据锁(MetaData Lock,MDL),MDL锁是表级锁的一种,专门用于保护数据库对象的元数据(结构定义), 可以将MDL锁简单分为MDL-S读锁和MDL-X写锁两种类型,其中读锁可以相互兼容而写锁是排他的,需要注意的是 MDL 锁的获取和释放都是隐式的 ,其中获取是在数据库操作时根据操作类型自动加锁,而释放则是在事务结束时(提交或回滚)自动释放。从MDL加锁类型上来说,DML操作通常会加MDL-S读锁(不涉及修改表结构),而DDL操作以INPLACE执行原理为例,该方法的执行分为准备、执行和提交三个阶段,在准备和提交阶段都会对表短暂加MDL-X写锁,而在长时间的执行阶段则会降级为MDL-S读锁(执行期间的MDL-S读锁保证了不会同时执行其他的 DDL,但可正常执行 DML),这也是该方式可以Online的关键。

不过,即使是 Online DDL 也需要注意避免 MDL 锁阻塞问题 ,即若存在长事务持有MDL-S共享读锁,且同时尝试执行不兼容的 MDL-X 写锁操作时,则后续所有尝试访问该表的操作(即使是 SELECT)都可能被阻塞,形成锁等待队列,导致数据库连接堆积甚至雪崩 。这是因为长事务执行DML操作访问数据库时会持有MDL读锁,此时以INPLACE方式执行Online DDL操作时首先在准备阶段需要短暂获取MDL写锁,此时会被阻塞等待DML长事务结束,而 MySQL 的 MDL 锁管理器维护着锁等待队列(FIFO,到达顺序排队等待), 与长事务MDL读锁不兼容的DDL MDL写锁必须等待,这样在MDL写锁之后到达的任何操作请求都会被阻塞、不能插队执行,这就会导致连接耗尽的雪崩效应。因此,在对表执行Online DDL变更时,务必选择在业务低峰期进行,且需要提前检查数据库表上是否存在长事务或死锁,谨慎评估各方案的限制与风险、做好数据备份和回滚方案。

5. 触发器、视图与存储过程

  1. 触发器TRIGGER:绑定到特定的数据库表上,并在该表发生指定的数据操作事件(INSERT, UPDATE, DELETE之前之后 自动触发执行预设的 SQL 代码块。缺点是性能开销大(尤其对于高频操作表),级联触发可能导致意外行为;
  2. 视图VIEW:视图本身不存储数据,是基于多个基础表(或其它视图)的查询结果定义的虚拟表,只是预定义的查询语句,用于简化查询。缺点是复杂视图查询性能较差,且受基础表变化影响和更新限制;
  3. 存储过程PROCEDURE:一组预编译存储在数据库服务器端 的 SQL 代码块和控制流语句的封装集合,可以被应用程序显式调用,可以接受输入参数、返回输出结果集。缺点是业务逻辑分散在应用层和数据库层、维护和测试困难,且逻辑在数据库服务端执行会增加数据库 CPU 和内存资源的消耗;

触发器、视图与存储过程三者的应用与开发需要较强的专业知识,且本身机制较为复杂、存在很多限制和隐藏问题,难以维护和管理,在没有特殊需求的场景中尽量不要使用。

相关推荐
亿坊电商1 小时前
利于SEO优化的CMS系统都有哪些特点?
前端·数据库
计算机程序设计小李同学2 小时前
平价药店销售与管理系统
java·mysql·spring·spring cloud·ssm
心丑姑娘2 小时前
使用ClickHouse时的劣质SQL样例
数据库·sql·clickhouse
什么都不会的Tristan2 小时前
redis篇
数据库·redis·缓存
only°夏至besos2 小时前
MySQL 运维实战:常见问题排查与解决方案
运维·数据库·mysql
液态不合群2 小时前
并发,并行与异步
数据库
Dxy12393102162 小时前
MySQL如何批量更新数据:高效方法与最佳实践
数据库·mysql
dishugj2 小时前
【Oracle】 Flashback(闪回)技术实操指南
数据库·oracle·flashback
白山云北诗2 小时前
中小企业如何做好企业官网的网络安全
网络·数据库·web安全·ddos·cc·企业网络安全