问题定义 SQL 语句 索引 TBL_XXX 表上有 idx_a、idx_b 两个非唯一索引。 问题 由于 A、B 非联合索引,因此只会用到 idx_a;当 A 列筛选出的行数很大时,结果集的每一行都要做回表,去判断是否满足 B 列条件,从而造成查询超时。 解决方案 改进后 SQL 语句 原理剖析 通过 EXPLAIN 指令可以发现,三条 SELECT 语句的 Extra 列有的是 Using where、有的是 Using intersect,这是因为 MySQL 根据 B 条件不同的取值计算出了不同的扫描行数,选择了该条件下它所认为的最优方式。 Using intersect 能够对多个扫描结果取交集,这里相当于是对 B 列每个条件得到的结果集 与 A 列分别取了交集做了多次查询,再将最后得到的结果集取并集,从而无需再回表;即使 SQL 语句后面还有 C = "xyz" 的非索引列条件,也仅需在交集上做回表,大幅降低了回表次数。 Using intersect 条件 SQL 语句为多个索引的 AND 条件时,满足: 如果是二级索引,必须是等值查询,且覆盖其中所有索引列 如果是主键索引,可以是范围查询 参考 索引合并,能不用就不要用吧!
数据库 标签下的文章
共 3 篇
目录 概述 日志 事务 类型 索引 优化 主从架构 概述 基础架构 MySQL 主要分为 Server 层和存储引擎层: Server 层:所有跨存储引擎的功能都在这一层实现,如存储过程、触发器、视图,函数等,还有一个通用的 binlog 日志模块。 查询缓存在 MySQL 8.0 后被移除,因为缓存失效在实际业务场景中可能会非常频繁,如果对一个表更新,这个表上的所有的查询缓存都会被清空。 存储引擎:主要负责数据的存储和读取,采用可替换的插件式架构,支持 InnoDB、MyISAM 等多个存储引擎,其中 InnoDB 自带 redo log 日志模块,采用聚簇索引,支持事务、行锁、外键等(以下所有内容都是基于 InnoDB 的)。 查询语句 先检查该语句是否有权限,如果没有权限直接返回错误信息;如果有,以这条 SQL 语句为 key 在内存中查询是否有缓存(MySQL 8.0 以前),无缓存则执行下一步。 通过分析器进行词法分析,提取 SQL 语句的关键元素,如这个语句是 select,查询的表名、列,以及查询条件等。然后判断是否有语法错误。 接下来优化器会根据自己的优化算法选择其所认为执行效率最高的方案(有时不一定最好),例如上面的 SQL 可以有两种执行方案: a. 先查询表中姓名为“张三”的学生,再判断年龄是否是 18 岁。 b. 先找出学生中年龄为 18 岁的,再查询姓名为“张三”的学生。 确认了执行计划后,进行权限校验,如果没有权限就会返回错误信息,否则调用数据库引擎接口,返回引擎的执行结果。 Server 层每从存储引擎读到一条记录就会发送给客户端,之所以客户端是直接显示所有记录的,是因为客户端是等查询语句完成后才会显示。 更新语句(两阶段提交) 在 InnoDB 引擎下,这个语句的执行流程如下: 先通过 where 条件查询到张三这条数据,如果该行数据不在 InnoDB Buffer Pool(内存)中,则从磁盘加载对应的数据页。 InnoDB 修改内存中的该行数据,同时生成 undo log(更新前的值,用于回滚)。 WAL(Write-Ahead Logging)机制:InnoDB 写入 redo log(保证宕机后可恢复数据),进入 prepare 状态,并通知执行器准备提交事务。 执行器记录 binlog(逻辑日志,用于主从复制、数据恢复),记录该语句。 执行器调用 InnoDB 引擎提交 redo log,将其改为 commit 状态,更新完成。 两阶段提交 上述过程中,redo log 的写入拆成了两个步骤 prepare 和 commit。 写入 binlog 时发生异常时:MySQL 根据 redo log 日志恢复数据时,发现 redo log 还处于 prepare 阶段,且没有对应 binlog 日志,就会回滚该事务。 redo log 在 commit 阶段发生异常时:虽然 redo log 处于 prepare 状态,但是能通过事务 id 找到对应的 binlog 日志,所以 MySQL 认为是完整的,就会提交事务。 日志 binlog MySQL 中的逻辑日志,用于记录语句的原始逻辑。数据备份、主从复制需要依靠 binlog 来同步数据。它有三种格式: statement:SQL 语句原文,但 update_time=now() 会获取当前系统时间。 row:update_time=now() 变成了具体的时间。 mixed:前两者的混合,因为 row 更占用空间,恢复与同步时会更消耗 IO 资源。 写入机制 一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为 binlog cache。 单个线程 binlog cache 的大小可以由参数 binlog_cache_size 控制,如果存储内容超过了这个参数,就要暂存到磁盘。 write 和 fsync 的时机,可以由参数 sync_binlog 控制。 在出现 IO 瓶颈的场景里,将 sync_binlog 设置成较大的值可以提升性能。 0:每次提交事务都只 write,由系统自行判断什么时候执行 fsync 。 1:每次提交事务都会执行 fsync ,防止宕机时 cache 中 binlog 丢失。 N(N>1):每次提交事务都 write,但累积 N 个事务后才 fsync。 undo log undo log 属于逻辑日志,记录的是 SQL 语句,比如事务执行一条 DELETE 语句,那 undo log 就会记录一条相对应的 INSERT 语句。同时,undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。当执行事务过程中出现错误或者需要执行回滚操作的话,MySQL 可以利用 undo log 将数据恢复到事
概述 数据结构 持久化 集群 事务 缓存 分布式锁 场景 一、概述 Redis 的优缺点 Redis 的优点?为什么快? 基于内存操作:绝大部分操作和数据都在内存中,相比传统磁盘文件操作减少了IO。 高效的数据结构:优化的 String、Hash、List、Set、Zset 等数据结构。 采用单线程:省去上下文切换和CPU的开销,同时不存在资源竞争,避免死锁。 单线程:命令执行使用单线程进行处理。因为 Redis 的瓶颈不是 CPU,最有可能是机器内存或网络带宽,并且单线程易于实现。 多线程:Redis 6.0 以后,多线程用于处理网络数据的读写和协议解析,充分利用 CPU 资源,减少网络 I/O 阻塞带来的性能损耗。 删除大 key 时使用 unlink 异步删除而不是 del,否则会造成单线程阻塞。 I/O多路复用:一个服务端进程(复用)同时处理多个套接字描述符(多路),根据 Socket 上的事件来选择对应的事件处理器进行处理。 Redis 的缺点?为什么不做主数据库只做缓存? 内存限制:数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。 数据持久化:尽管采用了数据持久化机制,如果服务器崩溃或断电,内存中数据仍可能丢失。 数据安全:不具备像主数据库一样复杂的认证和审计机制。 结构化查询:作为键值(Key-Value)数据库,对结构化查询支持较差。 事务处理:对复杂的事务无能为力,比如跨多个键的事务处理。 在线扩容:在集群容量达到上限时在线扩容会变得很复杂。 为什么用 Redis 而不用 Map 做缓存? Map 实现的是本地缓存,其生命周期随着 JVM 的销毁而结束,且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性;而 Redis 的分布式缓存具有一致性,各实例共用一份缓存数据。 Redis 可单独部署,在多个项目间共享。 Redis 的缓存可以持久化,Map 是内存对象,程序一重启数据就没了。 Redis 可以用几十G内存来做缓存,Map 不行。 Redis 可以处理每秒百万级的并发。 Redis 缓存有过期机制和丰富的 API。 Redis 应用场景 缓存热点数据:缓解数据库的压力。用户在访问业务数据时,先到 Redis 中拿;如果不存在,再到 MySQL 中拿,接着把访问过的数据写入 Redis。 社交网络:Redis 的哈希、集合等数据结构能很方便的的实现排行榜、共同好友等功能;利用 Redis 原子性的自增操作,可以实现计数器的功能,比如统计用户点赞数等;也可作限速器,如秒杀场景中防止用户快速点击带来不必要的压力。 消息队列:Redis 提供了发布/订阅模式及阻塞队列功能,能够实现简单的消息队列,实现异步操作。 分布式锁:分布式场景下,无法使用单机环境下的锁对多个节点上的进程同步。可以使用 Redis 自带的 SETNX(SET if Not eXists)命令或 RedLock 分布式锁实现。 数据过期策略 Redis 采用了惰性删除和定期删除相结合的过期策略: 惰性删除:不主动删除过期键,访问 key 时再检测是否过期,如果过期则删除。这种方式对 CPU 友好,但如果 key 过期后一直没有使用,则在内存中永远不会释放。 定期删除:每隔一段时间取出一些 key 进行检查并删除过期 key。分两种模式,SLOW 模式是定时任务,FAST 模式执行频率不固定,但两次间隔不低于 2ms。 数据淘汰策略 Redis 的内存不够用时,有 8 种策略来选择要删除的 key: noeviction:默认,不淘汰任何 key,内存满时拒绝写入。 volatile-ttl:优先淘汰更早过期的 key。 volatile-random:随机淘汰设置了过期时间的 key。 volatile-lru:淘汰所有设置了过期时间中最近最久未使用的 key。 volatile-lfu:淘汰设置了过期时间中最少频率使用的 key。 allkeys-random:随机淘汰任意 key。 allkeys-lru:淘汰最近最久未使用的 key。 allkeys-lfu:淘汰最少频率使用的 key。 Redis 如何做内存优化? 尽可能的将数据模型抽象到一个哈希表里面。比如一个用户对象,不要为这个用户的名称,邮箱等设置单独的 Key,而是将这个用户的所有信息存储到一张哈希表里。 二、数据结构 String 底层由 *SDS*(简单动态字符串)实现:具有 len(字符串长度 O(1) 查询)、alloc(分配给字符数组的空间长度)、flags(类型)、buf[](字符数组)属性;拼接前会自动扩容。用于: 缓存对象:JSON 等。 共享Session信息:解决了分布式系统下多服务器 Session 不一致的问题。 分布式锁:利用 SETNX 命令。 计数器:支持原子性数值操作,用于访问次数、点赞、库存等。 Hash