19. 怎么解决这些问题呢?MySQL的默认隔离级别是?
解决脏读、不可重复读、幻读这些问题,主要是通过设置事务隔离级别。MySQL 支持四种隔离级别,从低到高分别是读未提交、读已提交、可重复读和串行化。
读未提交基本解决不了问题,读已提交可以解决脏读,但还可能有不可重复读和幻读。可重复读可以解决脏读和不可重复读,也是 MySQL InnoDB 的默认隔离级别。串行化隔离性最强,可以避免这些并发问题,但性能最低。
需要注意的是,MySQL InnoDB 的可重复读不是单纯靠隔离级别表格来实现的,它通过 MVCC 保证普通查询的一致性读,通过 next-key lock 在当前读场景下避免幻读,所以 MySQL 的 RR 比标准意义上的 RR 更强一些。
20. undo log和redo log的区别是什么?
redo log 和 undo log 都是 InnoDB 的事务日志,但作用不一样。
redo log 是重做日志,记录的是数据页的物理修改,主要用于崩溃恢复。比如事务提交了,但数据页还没来得及刷盘,只要 redo log 持久化了,MySQL 重启后就可以根据 redo log 把修改恢复回来,所以它保证的是持久性。
undo log 是回滚日志,记录的是数据修改前的旧值或者反向操作,事务回滚时可以用它把数据恢复到修改前,所以它主要保证原子性。另外 undo log 还会形成版本链,支持 MVCC 的快照读。
简单说,redo log 是为了提交后的数据不丢,undo log 是为了出错时可以回滚,也可以支持 MVCC 读历史版本。
21. 事务中的隔离性是如何保证的呢?(你解释一下MVCC)
事务的隔离性主要靠锁和 MVCC 来保证。简单说,普通 select 这种快照读主要靠 MVCC,update、delete、select for update 这种当前读主要靠锁。
MVCC 的核心是让一行数据保留多个版本,这样读和写不用互相阻塞。它底层主要依赖隐藏字段、undo log 和 ReadView。每行数据里有隐藏的 trx_id 和 roll_pointer,trx_id 表示最后修改这行数据的事务 id,roll_pointer 指向 undo log 中的旧版本,所以多个旧版本可以串成一条版本链。
当事务做普通查询时,会生成一个 ReadView,用它判断当前版本是否对自己可见。如果当前版本不可见,就沿着 undo log 版本链往前找,直到找到一个可见版本。
RC 和 RR 的区别主要在 ReadView 的生成时机:RC 是每次查询都生成新的 ReadView,RR 是事务第一次查询生成 ReadView,后面复用,所以 RR 能实现可重复读。
22. MySQL主从同步原理是什么?
MySQL 主从复制主要是基于 binlog 实现的。主库发生数据变更并提交事务后,会把变更记录到 binlog 里。
从库会通过 I/O 线程连接主库,主库会启动 dump 线程把 binlog 发送给从库。从库的 I/O 线程拿到 binlog 后,会先写到自己的 relay log,也就是中继日志里。然后从库的 SQL 线程再读取 relay log,按顺序执行里面的事件,把主库的变更同步到从库。
所以整体流程就是:主库写 binlog,从库拉 binlog 写 relay log,然后从库回放 relay log。需要注意的是,主从复制通常是异步的,所以可能会有主从延迟,读写分离时刚写完马上读从库,可能读不到最新数据。
23. 你们项目用过MySQL的分库分表吗?
严格来说,我们项目没有做传统意义上的水平分库分表。我们更多是微服务架构下的垂直拆库,也就是按业务边界拆,比如不同服务维护自己的数据库,这样可以降低模块之间的耦合。
但我们没有把一张大表按用户 id 或订单 id 拆到多个库、多个表里,因为目前项目的数据量和并发还没到这个程度。如果强行上水平分库分表,反而会带来分布式 ID、跨库查询、跨库事务、分页排序、扩容迁移这些复杂问题。
24. 那你之前使用过水平分库吗?
用过,但规模不算特别复杂。我们当时有一张业务核心表,数据量到了千万级,而且查询和写入都比较集中。前面已经做过索引优化、SQL 优化和部分缓存,但高峰期单库压力还是比较大,所以后面做了水平分库。
当时我们用 MyCat 做分片路由,部署了 3 个数据库实例,按照业务主键做取模分片,比如 id % 3 路由到不同数据库。历史数据也按照同样规则做迁移,保证新老数据都能按统一规则访问。
做完以后,单库的数据量和读写压力被拆开了,性能确实有改善。但这个方案也有代价,比如跨库查询、分页排序、扩容迁移都会变复杂。所以我们当时主要要求核心查询都尽量带上分片键,避免全库扫描。