第一章 架构与历史 逻辑架构: 客户端 =>连接/线程处理 =>查询缓存/解析器 =>优化器 =>存储引擎.
其中只有innodb引擎会解析SQL中的外键定义.其他引擎不会解析SQL. 所有内容都应该同时关注两个层面的实现: 服务器层和存储引擎层.
1.1.1 连接管理 每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个单独的线程中执行. 服务器会负责缓存线程,因此不需要为每个新连接创建或销毁线程.
1.1.2 优化与执行 首先查询缓存中有没有,如果没有:
Mysql会解析查询,并创建内部数据结构(解析树),然后进行优化,包括重写查询,决定表的读取顺序以及选择合适的索引.
用户可以使用hint指令指导优化.
优化器并不关心底层存储引擎的具体实现,但会向底层存储引擎请求一些数据.
1.2 并发控制 1.2.1 读写锁 读锁: 共享锁,不干扰其他锁. 写锁: 排他锁,排除其他读写锁.
1.2.2 锁粒度
表锁 开销最小,但并发度低. // ALTER TABLE语句都会使用表锁,不管底层引擎是什么.
行锁 最大程度的并发,但也有最大的锁开销. 由存储引擎各自实现.
1.3 事务 基本流程:
Start transaction;
执行一组SQL
commit.
事务的4个指标(ACID):
原子性: 要么全部成功,要么全部回滚.
一致性: 事务中间状态的修改不会保存到数据库中.(感觉和原子性有重合)
隔离性: 事务在最终提交之前,对其他事务是不可见的.
持久性: 一旦事务提交,修改就会永久保存到数据库中.
1.3.1 隔离级别 4种隔离级别(由低到高):1.Read uncommitted:
未提交读. 有脏读. 能读到别的事务还没commit的修改. 太弱, 基本不用.
2.Read committed (Mysql以外的数据库默认级别)
提交读. 一个事务的修改,提交前对其他事务不可见. 存在不可重复读问题. 比如如下流程:
A事务读V;
B事务提交V;
A事务读V. 由于B事务中间提交了一次,A事务两次读到的值不一样,也就是不可重复读.
3.Repeatable Read (Mysql默认级别)
可重复读. 可以重复读单条记录. (方法,InnoDb增加多版本并发控制MVCC) 存在幻读问题. 比如如下流程:
A事务读范围行Vs;
B事务插入v到Vs中;
A事务读范围行Vs. 由于B事务中交提交了一次,A事务两次读到的范围行不一样,也就是幻读. (可以用MVCC+间隙锁解决.)
4.Serialable
可串行化. 解决了幻读. 强制事务串行执行. 增加了每一行锁.
总结问题:
未提交读: 脏读,不可重复读,幻读.
提交读: 不可重复读,幻读.
可重复读: 幻读.
可串行: 慢.
mysql默认是可重复读,解决正确使用的幻读,使用MVCC+间隙锁; MVCC: 解决两次快照读的幻读; (两次快照读一定一样) 间隙锁: 解决两次当前读的幻读; (两次当前读一定一样)
而,快照读和当前读的结果对于mysql的可重复读
级别来说,可能结果不一样。 因此我们定义的正确使用: 程序员不应假设”快照读”和”当前读”的结果一样。
快照读: select xxx 当前读: select xxx for update; 或者update/insert语句中隐含的读(基于当前数据库的数据状态)
如果在最高隔离级别(串行)下,事务中的所有读都会被加上间歇锁, 因此保证了快照读和当前读的结果。// 也就是即使程序员不懂这方面知识、不正确使用,也能得到正确的结果。
1.3.2 事务死锁 Innodb: 检查到死锁的循环依赖 =>立即返回错误.
处理死锁: 将持有最少行级写锁(排他锁)的事务进行回滚.
1.3.3 事务日志 WAL(Write-Ahead Logging) 异步刷盘
.
1 2 1. 数据追加写到日志里(顺序IO); 2. 数据修改到原数据里. (随机IO).
第二步如果失败崩溃,可以利用第一步的日志修复.
1.3.4 Mysql中的事务 Mysql事务型引擎: InnoDB, NDB Cluster. (MyIsam不支持事务) 第三方引擎: XtraDB,PBXT InnoDb支持所有4个隔离级别.
自动提交 Mysql默认使用自动提交. 如果不显式得开始一个事务,每个查询都被当成一个独立的事务.
可能导致强制提交事务的命令:
Alter Table
Lock Tables
其他导致大量数据更改的语句…
在事务中混合使用存储引擎(不推荐)
服务层: 不管理事务;
存储引擎: 具体实现事务.
混合使用InnoDb,MyISAM: (事务型表+非事务型表)
回滚时,非事务型表上的变更无法撤销, 导致数据库处于不一致状态.
隐式和显式锁定 InnoDb采用两阶段锁定协议.
1 2 加锁: 事务执行过程中随时加锁; 解锁: 仅当Commit或RollBack时.
隐式加锁: 存储引擎自动加锁 显式加锁: 明确指定语句: Select ... Lock In Share Mode
tip
尽量不要显式加锁,而是交给Innodb实现. 以避免无法预料的错误. (除非研究得很深了.)
1.4 多版本并发控制 MVCC MVCC: 行级锁的一个变种. 在很多情况下避免了加锁操作, 开销更低. 大多实现了非阻塞的读操作, 写操作只锁定必要的行.
实现 每个事务开始的时候保存数据在某个时间点的快照.
InnoDb的MVCC (空间换时间,少加锁)
每行记录后保存两个隐藏列:
行的创建系统版本号;
行的删除系统版本号.
系统版本号反映了时间. 每开始一个新的事务,系统版本号都会递增. 事务开始时刻的系统版本号会作为事务的版本号, 用来和查询到的每行记录的版本号比较.
##Innodb的可重复读级别下Select
1 2 1. 创建版本号<=当前事务版本的数据; 2. 删除版本号>当前事务版本的数据 (或者没有删除).
Insert
Delete (标记删)
1 2 1. 删除的行,删除版本号=当前事务版本号. (打标记,而不是真的删)
Update (标记删+插入新)
1 2 1. 插入新行,创建版本号=当前事务版本号. 2. 原来的行,删除版本号=当前事务版本号.
底层原理 实际实现参见: https://liuzhengyang.github.io/2017/04/18/innodb-mvcc/ 是将旧版本的行存在undo log中,如果没有依赖这部分undo log的事务结束了(提交或者回滚), 这部分undo log是会销毁的,因此不会造成永久的存储负担。
MVCC与隔离级别:
读未提交: 不使用MVCC;
读已提交: 使用MVCC; // 可能版本号条件不同
可重复读: 使用MVCC+当前读间隙锁; // 解决正确使用情况下的幻读
可串行读: MVCC+快照读和当前读都有间隙锁. // 解决所有情况下的幻读
MVCC相当于乐观锁或者无锁、空间换时间; 间隙锁相当于悲观锁。真实地锁了索引。
1.5 Mysql的存储引擎
元数据
(表定义): 表同名的.frm
文件; (服务层统一实现)
数据
; (存储引擎分别实现)
索引
. (存储引擎分别实现)
元数据(表定义具体内容) 表的相关属性:
Dynamic: 行长度可变,包含Varchar或Blob的行.
Fixed: 只包含固定长度列.
Compressed: 压缩表.
Rows: 行数. Innodb:估算值. MyISAM: 精确值;
Index_length: 索引长度
Collcation: 默认字符集和字符排序规则.
其他信息…
1.5.1 InnoDB存储引擎 设计目标: 大量短期事务. Mysql主推引擎.
存储 数据: 表空间中,一系列数据文件组成. 索引: 表空间中,独立的索引文件.
隔离级别的实现:
MVCC;
间隙锁: 不仅锁定查询涉及的行,还锁定索引中的间隙,防止幻读.
存储格式:(跨平台)
主键: 指向物理地址;
二级索引: 非主键索引,指向主键,所以主键应尽可能小.
MyISam 存储
数据: .MYD
索引: .MYI
变长行: 默认最大数据量256TB,因为指向数据记录的指针长度是6B. 可以修改Max_Rows和Avg_row_length的值,来改变指针长度. (两者相乘=表最大容量)
并发 整张表加锁.
索引 支持全文索引,基于分词创建的索引.
压缩表 不再修改的表可以进行压缩,提高查询性能,减少磁盘空间.
1.5.3 其他引擎 Archive: 只支持插入和查询,不支持修改和删除. BlackHole: 丢弃所有插入的数据,只记录日志. 有很多问题. CSV: 存储CSV文件. (这个场景应该考虑使用Sql Server) Federated: 访问其他Mysql服务器的代理,默认禁用.
Memory: 磁盘只保存结构,数据在内存,重启后丢失数据. 使用场景:
查找或映射表;// 邮编和州名的映射表;
缓存周期性聚合数据;
保存数据分析中产生的中间数据.
限制:
表级锁;
只支持定长列, 如果指定了varchar,会转换成char.
Merge引擎: 多个MyIsam表合并的虚拟表; 引入分区功能后弃用.
NDB引擎: Mysql集群版.
1.5.5 选择合适的引擎 场景:
全文索引: InnoDB + Sphinx
在线热备份: InnoDB
记录日志: MyISAM,Archive
10TB以下: InnoDB
10TB以上: InfoBright.
大部分情况优先考虑: Innodb