1. 什么是表锁?什么是行锁?什么情况下会使用表锁?
InnoDB引擎通过“索引”实现行锁(锁定满足条件的行),但如果操作无法通过索引定位行,会导致行锁失效,进而升级为表锁。常见的表现为:
(1)条件中未使用索引,InnoDB 无法定位具体行,会锁整个表;
(2)使用非索引列的范围查询,范围查询无法通过索引锁定行,触发表锁;
(3)索引失效(如函数 / 类型转换),索引失效后无法定位行,触发表锁;
(4)更新全表的操作,因需更新所有行,行锁效率低于表锁。
InnoDB 使用表锁的核心场景可分为 “主动使用” 和 “被动退化” 两类,本质是当 “行锁无法高效实现” 或 “表锁成本更低” 时的选择:
(1)主动使用表锁的场景(显式或隐式):场景 1:无索引 / 索引失效导致的全表扫描更新、场景 2:执行 LOCK TABLES
显式锁表、场景 3:DDL 操作(数据定义语言):所有 DDL 操作(如 ALTER TABLE
、DROP TABLE
、CREATE INDEX
等)会自动加 表级排他锁,防止 DDL 过程中表数据被修改导致结构不一致。
(2)被动退化到表锁的场景:这类场景是 InnoDB 尝试加行锁失败后,被迫升级为表锁:场景:行锁冲突过于频繁,触发 “锁升级”:InnoDB 虽然支持行锁,但每个行锁的维护(如锁结构存储、冲突检查)需要消耗内存。当一个事务需要锁定 极多的行(如锁定数万行),且行锁冲突频繁时,MySQL 可能会触发 锁升级(Lock Escalation)—— 将大量行锁合并为一个表锁,减少内存消耗和冲突检查成本。
2. 责任链设计模式?策略模式?模板方法模式?
先明确三者的 “本质定位”—— 不同模式解决的核心问题完全不同:
模板方法模式:解决 “步骤固定但细节可变” 的问题(定义流程骨架,留空细节);
策略模式:解决 “多种算法 / 行为可选” 的问题(封装不同实现,动态切换);
责任链模式:解决 “多个对象依次处理请求” 的问题(请求传递,直到被处理)。
使用场景:
模板方法模式:适合 “流程固定,细节可变” 的场景;
策略模式:适合 “多种算法可选,需动态切换” 的场景;
责任链模式:适合 “请求需多步处理,且处理者不确定” 的场景。
(1)责任链模式
核心是 “将多个处理器(Handler)连成一条链,请求沿着链传递,使用多个节点来处理它”。它的存在主要是为了解决三类核心问题:①解耦 “请求发送者” 与 “请求处理者”:传统写法中,发送者需要知道哪个处理器能处理请求,比如说使用if-else来判断,一旦处理器增减或逻辑变化,发送者代码必须修改。责任链模式中,发送者只需将请求 “丢给链的头部”,无需关心链上有多少处理器、谁来处理 —— 处理器的增减 / 顺序调整,完全不影响发送者。②支持 “动态组合处理流程”:责任链的处理器可以动态添加、删除或调整顺序,灵活适配不同场景。③避免 “if-else/switch” 的代码臃肿:当处理逻辑有多个分支且可能扩展时,if-else
会导致代码冗长、可读性差,责任链用 “对象链” 替代分支判断,代码更符合单一职责原则(每个处理器只处理自己负责的逻辑)。
(2)模板方法模式
核心定义:定义一个固定的流程骨架(父类),将流程中 “可变的步骤” 延迟到子类实现,确保流程的一致性,同时允许细节灵活调整。
核心思想:“骨架不可变,细节可变”,是一种 “父类定规矩,子类填内容” 的模式。
(3)策略模式
核心定义:将多种可替换的算法 / 行为封装成独立的 “策略类”,使算法与使用算法的 “上下文” 解耦,上下文可动态切换不同策略(无需修改原有代码)。
核心思想:“算法家族化,切换动态化”,是一种 “选择不同实现” 的模式。
3. MySQL中有哪些事务隔离级别?
读未提交、读已提交、可重复读、串行化。
(1)读已提交如何解决脏读?
脏读:一个事务读取到另一个事务未提交的修改(可能被回滚的数据)。
RC 级别通过 **“只读取已提交的数据”** 解决脏读,核心机制是:
每次读取都获取最新的已提交版本:事务中每次执行
SELECT
时,都会去读取其他事务已经提交的数据版本,忽略未提交的修改。实现方式:依赖 MySQL 的多版本并发控制(MVCC)。每个事务修改数据时,会生成一个新的数据版本,并标记版本号(与事务 ID 关联)。RC 级别下,查询只会看到 “版本号小于当前事务 ID 且已提交” 的数据,因此不会读取到未提交的脏数据。
(2)可重复读如何解决脏读和不可重复读?
1. 解决脏读的机制:与 读已提交 级别类似,可重复读 也通过 MVCC 保证 “只读取已提交的数据”,但对 “已提交版本” 的判断更严格:
事务启动时会生成一个一致性快照(基于当时的全局事务 ID),整个事务内的所有
SELECT
都读取这个快照中的数据。快照中只包含 “在事务启动前已提交的版本”,完全忽略事务启动后其他事务的未提交修改,因此不会出现脏读。
2. 解决不可重复读的机制:可重复读 通过 **“事务内读取一致性快照”** 解决不可重复读:
事务启动时生成的快照会被整个事务复用,无论其他事务是否提交新的修改,本事务内的
SELECT
始终读取快照中的旧版本,确保多次读取结果一致。
(3)可重复读如何解决幻读?
幻读:同一事务内两次范围查询,结果因其他事务插入新数据而增多(“幻觉” 出新行)。
MySQL 的 InnoDB 引擎在 可重复读 级别通过 **“MVCC 快照读 + 间隙锁当前读”** 组合解决幻读:
快照读(普通
SELECT
):
依赖一致性快照,事务内两次范围查询都读取快照数据,其他事务插入的新数据不在快照中,因此不会看到 “新增的行”。当前读(加锁查询 / 写操作,如
SELECT ... FOR UPDATE
、INSERT
):
通过间隙锁(Gap Lock) 锁定 “可能插入新数据的区间”,阻止其他事务在该区间插入数据,从源头避免新行产生。
4. 布隆过滤器
布隆过滤器的核心作用是快速判断一个元素 “是否可能存在”,存在一定的 “误判率”(但不会漏判)。
原理:通过多个哈希函数将元素映射到一个位数组的多个 bit 位,标记为 1;查询时,若所有对应 bit 位都是 1,则 “可能存在”,否则 “一定不存在。
特点:优势:空间效率极高(用 bit 存储)、查询速度快(O (k),k 为哈希函数数量);局限:有误判率(可能把不存在的元素判为 “可能存在”),且不支持删除操作(删除会影响其他元素)。
典型使用场景:缓存穿透防护:在缓存前加一层布隆过滤器,提前过滤掉 “一定不存在的 key”,避免请求穿透到数据库(如恶意查询不存在的 ID);海量数据去重:如爬虫 URL 去重(判断 URL 是否已爬取)、邮件黑名单过滤等。
5. BitMap实现签到
(1)Key 的设计
用于唯一标识用户的签到记录,通常采用 “业务前缀:用户 ID: 时间维度” 的结构化命名,例如:user:sign:1001:2024
(用户 ID=1001 在 2024 年的签到记录)
时间维度根据业务需求选择(年 / 月 / 日),推荐按 “月” 拆分,避免单 Bitmap 过大。
(2)Value 的设计
Bitmap 的 value 是一个 二进制数组(bit 序列),每个 bit 位对应一天的签到状态:1
表示该天已签到,0
表示未签到。位的索引(offset)对应 “当月的第几天”(通常从 0 开始,如 0 代表 1 号,1 代表 2 号,以此类推)。
(3)具体实现
①签到操作(记录签到状态):逻辑:用户签到时,将对应日期的 bit 位设为 1。命令:SETBIT key offset 1;
②统计签到情况:检查某天是否签到:GETBIT key offset
→ 返回 1 表示签到,0 表示未签到。
③统计当月签到总次数:BITCOUNT key
→ 统计 bitmap 中 1 的总数。
④计算连续签到天数:从当天 offset 往前遍历,找到第一个 0 的位置,当前 offset 与该位置的差值即为连续天数。
⑤查找当月签到的所有日期:遍历 bitmap 所有 bit 位,记录值为 1 的 offset,再转换为具体日期。
(4)优势
极致省内存:1 个月(30 天)的签到记录仅需 30 bit(约 4 字节),1000 万用户存储 1 年也仅需约 45MB。
操作高效:基于位运算,签到和统计的时间复杂度均为 O (1) 或 O (n)(n 为天数,通常很小)。
(5)注意事项
时间维度拆分:避免按年存储(365 天)导致单 bitmap 过大,推荐按月拆分(最多 31 位)。
offset 计算:需确保日期与 offset 正确映射(如 1 号对应 0,2 号对应 1),避免错位。
过期清理:对于过期的签到记录(如去年的月度数据),可通过 EXPIRE
设置过期时间自动清理。