事务隔离级别
READ-UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ: 多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍
有可能发生。
SERIALIZABLE: 完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰
*脏读* 指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。
*不可重复读* 指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
*幻读* 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.*
索引
索引类型总结,
- 按照数据结构维度划分:
B+Tree :MySQL 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。
哈希索引:类似键值对的形式,一次即可定位。
全文索引:对文本的内容进行分词,进行搜索。通常使用 ElasticSearch 代替。 - 按照底层存储方式⻆度划分:
聚簇索引:索引和数据一起放,InnoDB 中的主键索引就属于聚簇索引。 (一步到位)
非聚簇索引:索引和数据分开放,辅助索引就属于非聚簇索引。MyISAM 不管主键还是非主键,使用的都是非聚簇索引。 - 按照应用维度划分:
主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
普通索引:仅加速查询。
唯一索引:加速查询 + 列值唯一(可以有 NULL)。
覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
联合索引:多列值组成一个索引,专⻔用于组合搜索。
MyISAM 引擎和 InnoDB 引擎索引结构
MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但两者的实现方式不一样。
- MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。
在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为非聚簇索引。 - InnoDB 引擎中,其数据文件本身就是索引文件。
其表数据文件本身就是按 B+Tree 组织的一个索引结构, 树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为聚簇索引,而其余的索引都作为 辅助索引 ,辅助索引的 data 域存储相应记录主键的值。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。
因此,在设计表的时候,不建议使用过⻓的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂
最左匹配原则
在使用联合索引时,MySQL 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合
索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇
到>、<才会停止匹配。在使用联合索引时,可以将区分度高的字段放在最左边,可以过滤更多数据
常⻅的导致索引失效的情况有下面这些:
- 创建了组合索引,但查询条件未遵守最左匹配原则;
- 在索引列上进行计算、函数、类型转换等操作;
- 以 % 开头的 LIKE 查询比如 like ‘%abc’;
- 查询条件中使用 or,且 or 的前后条件中有一个列没有索引,涉及的索引都不会被使用到
redo log
redo log是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。 比如 MySQL 挂了,重启时,InnoDB存储引擎会使用redo log恢复数据。每条 redo 记录由“表空间号+数据⻚号+偏移量+修改数据⻓度+修改的数据”组成
redo log 是以一个日志文件组的形式出现的,每个的redo日志文件大小都是一样的。 比如可以配置为一组4个文件,每个文件的大小是 1GB,整个 redo log 日志文件组可以记录4G的内容。它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示:
binlog
redo log 是物理日志,记录内容是“在某个数据⻚上做了什么修改”。而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”。不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志.
MySQL的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据。
binlog 日志有三种格式可以指定:statement,row,mixed
指定statement,记录的内容是SQL语句原文, 如下SQL语句:
update T set update_time=now() where id=1
同步数据时,会执行记录的SQL语句,但是有个问题,update_time=now() 这里会获取当前系统时间,直接执行会导致与原
库的数据不一致。 为了解决这种问题,我们需要指定为row,记录的内容不再是简单的SQL语句了,还包含操作的具体数据
update T set update_time=1627112756247 where id=1这样就能保证同步数据的一致性,但是这种格式会把批量操作展开成多条单行SQL,比较占用空间。所以就有了一种折中的mixed,MySQL会判断这条SQL语句是否可能引起数据不一致,如果是,就用row格式,否则就用statement格式
两阶段提交:将redo log的写入拆成了两个步骤prepare和commit
解决的是发生一些特定异常的时候用redo log和binlog恢复数据不椅子的问题。如下示例:
redo log让InnoDB存储引擎拥有了崩溃恢复能力。 binlog保证了MySQL集群架构的数据一致性。 虽然它们都属于持久化的保证,但是侧重点不同。 在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。
我们以update语句为例,假设id=2的记录,字段c值是0,把字段c值更新成1, SQL语句为
update T set c=1 where id=2
假设执行过程中写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况?
由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。之后用binlog日志恢复数据时,就会少这一次更新,恢复
出来的这一行c值是0,如果用redo log日志恢复,这一行c值是1,最终数据不一致。
为了解决两份日志之间的一致问题,InnoDB使用两阶段提交。**将redo log的写入拆成了两个步骤prepare和commit。**使用两
阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据 redo log日志恢复数据时,发现redo log还处于prepare阶段,就会回滚该事务
MVCC
原生的锁,锁住一个资源后会禁止其他任何线程访问同一资源。但是很多应用都是读多写少的场景,很多数据的读取次数远大于修改的次数。这个思想和Java中的ReadWriteLock非常类似。读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥。 之后人们发现并发读还是不够,又提出了能不能让读写之间也不冲突的方法,就是在读取数据时通过副本的方式将数据保存下来,这样读锁就和写锁就不冲突了,这个思想很像Java中的CopyOnWriteArrayList。当然副本是一种概念,不同的数据库、不同的版本,可能会用不同方法去实现。
MySQL的MVCC就是基于保存历史版本的数据来实现的。
MVCC只在read-committed和repeatable-read两个隔离级别下工作,而read-committed和repeatable-read的区别就在于它们生成ReadView的策略不同。(ReadView中有个id列表trx_ids来存储系统中当前活跃着的读写事务,也就是begin了还未commit或rollback的事务。)read-committed隔离级别下的事务每次执行查询都会生成一个新的ReadView,而repeatable-read隔离级别则在第一次查询时生成一个ReadView,之后的读都复用之前的ReadView。
两种隔离级别的差异是时间上的概念,其实二者是一样的,重新创建一个ReadView并没有改变整体流程,这是一个刷新的动作而已