Notes on MySQL Overview
读书笔记:《 MySQL 实战 45 讲》,MySQL 基本原理介绍,极客时间笔记
MySQL 基础架构,主要包括 Server 层和储存引擎层
- Server 端
- 连接器:管理连接、权限验证
- 分析器:词法分析、语法分析
- 优化器:执行计划生成、索引选择
- 执行器:操作引擎、返回结果
- 存储引擎层负责数据的存储和提取。场景的存储引擎有 InnoDB( 5.5 之后为默认)、MyISAM、Memory
逻辑架构图:
日志系统 redo log 和 binLog
1 | create table T(ID int primary key, c int); |
InnodDB redo log
如果每次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高。为了解决这个问题,MySQL的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。
具体来说,当有一条记录需要更新的时候,InnoDB引擎就会把记录写到redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候,这就像打烊以后掌柜做的事。
- WAL技术:Write-Ahead Logging,先写日志,再写磁盘
- 当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log里,并更新内存。其它合适时间再写入磁盘
有了redo log,Inno DB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe
MySQL binlog(归档日志)
redo log是 InnoDB 引擎特有的日志,而Server层也有自己的日志,称为binlog。
redo log 和 binlog 不同点
- redo log 是 InnoDB 引擎特有的,binlog 是 MySQL的 Server 层实现的,所有引擎都能用
- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑
- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的,写到一定大小会切换到下一个文件,不会覆盖以前的日志
我们来看执行器和 InnoDB 引擎在执行这个简单的 update 语句时的内部流程。
- 执行器先找引擎取 ID = 2 这一行。 ID 是主键,引擎直接用树搜索找到这一行。如果ID = 2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回
- 执行器拿到引擎给的行数据,把这个值加上 1 ,比如原来是 N , 现在就是 N+1 ,得到新的一行数据,再调用引擎接口写入这行新数据
- 引擎将这行数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状体啊。然后告知执行期执行完成了,随时可以提交事务
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成 提交(commit)状态,更新完成
redolog两阶段提交
redo log 和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
事务隔离
事务:ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
隔离级别:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )
事务隔离的实现:在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
长事务:系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
事务启动方式
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback
- set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接
建议总是使用set autocommit=1, 通过显式语句的方式来启动事务。避免长连接导致长事务。
查询长事务
1 | select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60; -- 查找持续时间超过60s的事务 |
深入浅出索引
索引常见模型
- 哈希表 ( 查询效率不高 )
- 有序数组 ( 增删效率低,适合静态存储 )
- 搜索树
InnoDB 的索引模型
索引类型
主键索引
主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)
非主键索引
非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)
1 | create table T( |
- 如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树 ;
- 如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引树,得到 ID 的值为500,再到 ID 索引树搜索一次。这个过程称为回表。
也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。
覆盖索引
如果执行的语句是select ID from T where k between 3 and 5,这时只需要查ID的值,而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引k已经“覆盖了”我们的查询需求,我们称为覆盖索引。
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
最左前缀原则
只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。
索引下推 (index condition pushdown)
1 | select * from tuser where name like '张%' and age=10 and ismale=1; |
InnoDB在(name,age)索引内部就判断了age是否等于10,对于不等于10的记录,直接判断并跳过。在我们的这个例子中,只需要对ID4、ID5这两条记录回表取数据判断,就只需要回表2次