1.执行步骤
2.Write-Ahead Logging 技术
这两种日志有以下三点不同。
①redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
②redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
③redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
redo log 用于保证 crash-safe 能力。
3.两阶段提交
将 redo log 的写入拆成了两个步骤:prepare 和 commit。目的是为了让两份日志之间的逻辑一致。
图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的:
4.事务
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )
Oracle 数据库的默认隔离级别其实就是“读提交”,Mysql是可重复读
5.为什么建议你尽量不要使用长事务
①长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
②长事务还占用锁资源,也可能拖垮整个库
6.MySQL 的事务启动方式
①显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
②set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。
7.索引的常见模型
①哈希表
定义:是一种以键 - 值(key-value)存储数据的结构,我们只要输入待查找的键即 key,就可以找到其对应的值即 Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。不可避免地,多个 key 值经过哈希函数的换算,会出现同一个值的情况。处理这种情况的一种方法是,拉出一个链表。
好处:增加新的 User 时速度会很快,只需要往后追加。
缺点:因为不是有序的,所以哈希索引做区间查询的速度是很慢的。
应用:哈希表这种结构适用于只有等值查询的场景
②有序数组
优缺点:查询快,插入慢
应用:有序数组索引只适用于静态存储引擎,比如你要保存的是 2017 年某个城市的所有人口信息,这类不会再修改的数据
③搜索树
二叉树、AVL树、红黑树(红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能优于AVL)、B树、B+树
8.索引
索引类型分为主键索引和非主键索引。
主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引。
非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引。
9.自增主键
页分裂和合并
主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。
10.覆盖索引
回表
由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段
11.最左前缀
最左前缀可以是联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符。
在建立联合索引的时候,如何安排索引内的字段顺序:
①第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的
②如果既有联合查询,又有基于 a、b 各自的查询,考虑的原则就是空间
12.全局锁
定义:全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
场景:全局锁的典型使用场景是,做全库逻辑备份。
single-transaction :
官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
single-transaction 方法只适用于所有的表使用事务引擎的库。
其它:既然要全库只读,为什么不使用 set global readonly=true 的方式呢:
①在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。
②二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。
13.表级锁
表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
①表锁的语法是 lock tables … read/write。与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
②MDL 不需要显式使用,在访问一个表的时候会被自动加上
在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。
读锁之间不互斥
读写锁之间、写锁之间是互斥的
14.行锁
MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
事务中,行锁是在语句执行时才加上的,不是事务开始就加上,但释放是统一在事务结束时才释放。根据这个特性,对于高并发的行记录的操作语句就可以尽可能的安排到最后面,以减少锁等待的时间,提高并发性能。
死锁和死锁检测的概念:
一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
减少死锁的主要方向,就是控制访问相同资源的并发事务量。
15.多版本并发控制(MVCC)
InnoDB 的行数据有多个版本,每个数据版本有自己的 row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据 row trx_id 和一致性视图确定数据版本的可见性。
对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
对于读提交,查询只承认在语句启动前就已经提交完成的数据;
而当前读,总是读取已经提交完成的最新版本。
16.change buffer
由于唯一索引用不上 change buffer 的优化机制,因此如果业务可以接受,从性能角度出发我建议你优先考虑非唯一索引。
应用场景:更新多,查询少
17.字符串字段创建索引的场景
直接创建完整索引,这样可能比较占用空间;
创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;
倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。
18.MySQL“抖”了一下
刷脏页操作和执行时机
参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认值是 75%
参数innodb_flush_neighbors 用来控制跟它相邻的数据页也还是脏页的话,也会被放到一起刷
19.InnoDB表
一个 InnoDB 表包含两部分,即:表结构定义和数据。
在 MySQL 8.0 版本以前,表结构是存在以.frm 为后缀的文件里。
而 MySQL 8.0 版本,则已经允许把表结构定义放在系统数据表中了,因为表结构定义占用的空间很小。
innodb_file_per_table控制表的数据存放位置: OFF:,表的数据放在系统共享表空间(存放在information_schema表中) ON:单独存放在.ibd文件中
20.数据库中收缩表空间的方法
alter table A engine=InnoDB 命令来重建表
花时间最多的步骤是往临时表插入数据的过程,如果在这个过程中,有新的数据要写入到表 A 的话,就会造成数据丢失。因此,在整个 DDL 过程中,表 A 中不能有更新。也就是说,这个 DDL 不是 Online 的。
而在 MySQL 5.6 版本开始引入的 Online DDL,对这个操作流程做了优化。
21.InnoDB 和 MyISAM
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高;
而 InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
在事务支持、并发能力还是在数据安全方面,InnoDB 都优于 MyISAM
22.count函数
按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(*),所以我建议你,尽量使用 count(*)。
23.order by排序
Extra 这个字段中的“Using filesort”表示的就是需要排序,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer。
sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。
rowid 排序:内存只留主键和排序的字段,排序后再回表
MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。
优化:联合索引和覆盖索引
24.索引失效
对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。
(1)显式的函数操作 (2)隐式的字段类型转换 (3)隐式的字符集转换
25.幻读
幻读仅专指“新插入的行”。
为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。
行锁有冲突关系的是“另外一个行锁”。
但是间隙锁不一样,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。
间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。
间隙锁: ()开区间 next-key lock : ( ] 前开后闭区间
间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。
26.ext-key lock
我们在分析加锁规则的时候可以用 next-key lock 来分析。但是要知道,具体执行的时候,是要分成间隙锁和行锁两段来执行的。
可重复读隔离级别遵守两阶段锁协议,所有加锁的资源,都是在事务提交或者回滚的时候才释放的。
读提交隔离级别下还有一个优化,即:语句执行过程中加上的行锁,在语句执行完成后,就要把“不满足条件的行”上的行锁直接释放了,不需要等到事务提交。
也就是说,读提交隔离级别下,锁的范围更小,锁的时间更短,这也是不少业务都默认使用读提交隔离级别的原因。
原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
原则 2:查找过程中访问到的对象才会加锁。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
1、查询过程中访问到的对象才会加锁,而加锁的基本单位是next-key lock(前开后闭);
2、等值查询上MySQL的优化:索引上的等值查询,如果是唯一索引,next-key lock会退化为行锁,如果不是唯一索引,需要访问到第一个不满足条件的值,此时next-key lock会退化为间隙锁;
3、范围查询:无论是否是唯一索引,范围查询都需要访问到不满足条件的第一个值为止;
lock in share mode 只锁覆盖索引,但是如果是 for update 就不一样了。 执行 for update 时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。
27.主备同步流程
主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写 binlog。
备库 B 跟主库 A 之间维持了一个长连接。主库 A 内部有一个线程,专门用于服务备库 B 的这个长连接。
一个事务日志同步的完整过程是这样的:
①在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量。
②在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的 io_thread 和 sql_thread。其中 io_thread 负责与主库建立连接。
③主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 binlog,发给 B。
④备库 B 拿到 binlog 后,写到本地文件,称为中转日志(relay log)。
⑤sql_thread 读取中转日志,解析出日志里的命令,并执行。
28.binlog日志格式binlog_format
row 格式的时候,binlog 里面记录了真实删除行的主键 id
为什么会有 mixed 这种 binlog 格式的存在场景?
推论过程是这样的:因为有些 statement 格式的 binlog 可能会导致主备不一致,所以要使用 row 格式。
但 row 格式的缺点是,很占空间。比如你用一个 delete 语句删掉 10 万行数据,用 statement 的话就是一个 SQL 语句被记录到 binlog 中,占用几十个字节的空间。但如果用 row 格式的 binlog,就要把这 10 万条记录都写到 binlog 中。这样做,不仅会占用更大的空间,同时写 binlog 也要耗费 IO 资源,影响执行速度。
所以,MySQL 就取了个折中方案,也就是有了 mixed 格式的 binlog。mixed 格式的意思是,MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式。
也就是说,mixed 格式可以利用 statment 格式的优点,同时又避免了数据不一致的风险。
29.join
①如果可以使用被驱动表的索引,join 语句还是有其优势的;
②不能使用被驱动表的索引,只能使用 Block Nested-Loop Join 算法,这样的语句就尽量不要使用;
③在使用 join 的时候,应该让小表做驱动表。