mysql
基础架构

「建议」
- 尽量使用长链接(建立连接很耗时)
- 执行大操作后,重新连接或者初始化连接资源(因为 mysql 使用的临时内存是存在连接对象里面的,只有当连接释放了才释放,所以可能导致 oom)
- 对于更新大的表,尽量关闭缓存,因为一个表更新的时候,和这个表相关的缓存都会清理掉。
日志
「WAL 技术:先写日志,在写磁盘」
「两阶段提交:保证两个日志的状态保持一致」

bin log
- binlog 会记录所有的逻辑操作,并且是采用“追加写”的形式,主要用来做归档
redo log
- redoLog 是重做日志文件是记录数据修改之后的值,用于持久化到磁盘中。redo log 包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。由引擎层的 InnoDB 引擎实现,是物理日志,记录的是物理数据页修改的信息,比如“某个数据页上内容发生了哪些改动”。当一条数据需要更新时,InnoDB 会先将数据更新,然后记录 redoLogBuffer 中,然后找个时间将 redoLog 的操作执行到磁盘上的文件上。
两种日志的不同
- redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
事务
「ACID:原子性、隔离性、一致性、持久性」
读未提交
一个事务还未提交,所做的改变就能被其它事务看到
读提交
一个事务提交后,所做的改变才能被其它事务看到
「每个 sql 语句都会创建一个视图」
可重复读
一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
「会在事务启动时创建一个视图,整个事务存在期间都用这个视图」
串行读
顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
产生的问题
- 脏读
脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据 - 不可重复读
事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果不一致。(可以通过加给扫描到的行加锁或换隔离性更高的事务来解决) - 幻读
A 事务读取了 B 事务已经提交的新增数据(仅指新插入的行)。注意和不可重复读的区别,这里是新增,不可重复读是更改(或删除)。select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。(通过加入间隙锁来解决)
索引
索引原理
- hash索引
优缺点:只适合等值查询即(key-value对),不适合范围查找 - 二叉树
优点:效率高 缺点:索引还需要存储到磁盘中,树越高。访问磁盘次数就越多 - b+树索引
将平衡二叉树变为n叉树,减少了磁盘访问次数 - 索引模型
主键索引:也称为聚簇索引,叶子结点存储的是整行数据
普通索引:也称为2级索引,叶子结点存储的是主键索引的值
「区别」:主键索引直接搜索主键索引树,非主键索引先是搜索非主键索引树,找到主键值,再去主键索引树上搜索,这个过程称为回表。所以应该尽量使用主键搜索。
索引失效
主要原因还是对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。
- 条件字段函数操作
- 隐式类型转换(如:string和int相比)
- 隐式编码转换 (解决方案:更改表的字符集,修改sql语句 CONVERT转换字符集)
索引优化
- 自增索引
每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录。 主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。 - 覆盖索引
减少回表次数 - 最左原则
索引是按照定义里出现的字段排序的(如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的) - 索引下推
在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 - change buffer
当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InooDB会将这些更新操作缓存在change buffer中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作(merge)。通过这种方式就能保证这个数据逻辑的正确性。
主键索引为了判断唯一性(必须把数据业读到内存中),所以不能使用change buffer 普通索引可以用上change buffer 所以对于写多读少的业务,使用普通索引 如果写完就伴随着查询,则数据先进入到change buff,然后再把数据页读到内存汇总 随后在触发merge过程,反而会增加维护代价。则可以关闭change buff。
锁
全局锁
MySQL提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句
全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都select出来存成文本。
「缺点」
如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。
「改进」
可以通过mysqldump来一致性备份。主要实现是启动一个事务,来拿到一致性视图(和可重复读一下),所以只支持有事务的引擎。
表锁
MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(MDL)
MDL不需要显式使用,在访问一个表的时候会被自动加上,当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
间隙锁
间隙锁锁的是两个值的中间
跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。
for update 语句不仅加上了行锁还会加上间隙锁,间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。
锁优化
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。 控制并发度减少死锁检测耗时。(限流)
主从
mysql 学习思维导图

本文使用 mdnice 排版