书名 :Mysql技术内幕 第二版
第一章
- mysql整体架构: 连接池,管理服务及工具组件,sql接口(dml,ddl等),解析器,优化器,缓冲(cache & buffer),存储引擎,物理文件。
- innodb 特性:mvcc,next key lock,二次写,插入缓冲,自动哈希索引等特性。
- innodb 属于单进程多线程架构。
第二章
- mysql 线程类型: IO thread(write、read、insert buffer、log io) ,master thread, 锁监控,错误监控,purge thread(5.6.5 开始默认使用,从master中剥离,用于undo资源回收),page cleaner thread(将刷脏页的任务委托至此)。
- 缓冲池内容
- 缓冲池中管理的数据页包括:索引页,数据页,undo页,insert buffer, adaptive hash index, 锁信息,数据字典信息等。innodb_buffer_pool_size控制使用内存大小,通过 innodb... pool_instances进行内存缓冲池实例配置(多个实例可以提高性能,存在缓冲池级别的互斥等待时)。
instances 讨论见https://dba.stackexchange.com/questions/115139/optimal->number-of-mysql-innodb-buffer-pool-instances
判定instances方法如下(照抄上面回答)
为了确定缓冲池是否存在争用,互斥锁SHOW ENGINE INNODB STATUS在高峰时间收集了十几个样本。
然后使用shell片段聚合它:
#!/bin/sh
cat $1.innodb | grep "Mutex at " | cut -d"," -f1 | sort | uniq -c > /tmp/tmp1.txt
cat $1.innodb | grep "lock on " | cut -d"-"
-f2- | sort | uniq -c > /tmp/tmp2.txt
cat /tmp/tmp1.txt /tmp/tmp2.txt | sort -n > $1.contention rm /tmp/tmp1.txt /tmp/tmp2.txt
这给出了这样的输出:
.....
4 lock on RW-latch at 0x7fb86b2c9138 created in file dict/dict0dict.c line 1356
6 lock on RW-latch at 0x7fb86b2c4138 created in file dict/dict0dict.c line 1356
12 lock on RW-latch at 0x7fb86b2d9538 created in file dict/dict0dict.c line 1356
20 lock on RW-latch at 0x7fb86b2db138 created in file dict/dict0dict.c line 1356
22 Mutex at 0x7fb86b28f0e0 created file btr/btr0sea.c line 139
30 lock on RW-latch at 0x7fb86b2ba938 created in file dict/dict0dict.c line 1356
36 lock on RW-latch at 0x7fb86b2bad38 created in file dict/dict0dict.c line 1356
71 Mutex at 0x7fb86b28ecb8 created file buf/buf0buf.c line 597
164 lock on RW-latch at 0x7fb86b28f0b8 created in file btr/btr0sea.c line 139
如果您看到缓冲池互斥等待的计数很高,那么就应该考虑多个缓冲池实例。在小于~48G的缓冲池上不太可能发生争用。
缓存池中默认最小的内存管理粒度为16k(页),以段、区、页不同粒度进行管理。
-
缓冲池页面淘汰通过LRU进行管理,不同于一般的LRU,其具体实现如下:
新读取的页先插入到LRU中的midpoint中(innodb_old_blocks_pct控制),即插入到队列的尾部37%处。midpoint后的页称为old,前称为new。利用innodb_old_blocks_time控制数据页从midpoint到new.page made young 为old到new的过程。 page not made young innodb_old_blocks_time导致页存放于old中,未能存放于new中的状态。优势: 避免热点页被意外清除出缓存。
ps: show engine innodb status 查看到的buffer pool 中database pages表示LRU管理的页面数量。其总量不等于BUffer pool size,因为不是所有的缓冲池页都使用LRU管理。 -
页压缩功能,分配方式如下(假如请求4k页面):
- 检查列表是否存在可用4k页,有则使用。
- 没有则检查8k页,将8k页划分为两个4k页面,将两个4k页面放入4k页列表中。
- 如果2失败,则请求一个16k页,将16k页划分为2个4k页,1一个8k页面,并且放入对应的列表中。
checkpoint机制
作用:通过lsn进行标识,刷新内存中脏页。使得redo log大小可控(无需记录checkpoint前的redo log)。
checkpoint类型如下:
- sharp checkpoint(数据库关闭时回写脏页)
- fuzzy checkpoint包括:
- master thread checkpoint
master线程主动刷脏- flush_lru_list checkpoint
为保证LRU缓存中存在一定的可用空闲页,当可用空闲页不足时,强制将LRU尾端刷脏.5.6后page clean线程中执行以免导致用户查询(读)线程堵塞.- async/sync checkpint(5.6后page clean线程中执行)
作用:redo日志不可用时使用(比如说批量更改,更改的内容远远大于redo log的容量时触发):
判定公式如下(此部分mysql文档中没有,类似于自适应刷写机制。):
checkpoint_age = redo_lsn - checkpoint_lsn
async water mark = 75%redo log file size
sync water mark = 90%redo log file size
1)大于75触发异步写(将一次写拆分为多次提交写,减少瞬间系统压力)。
2)大于90出发同步写(减少redo占用)dirty page too much- 脏页在缓冲池中的占比超过阈值进行checkpoint(阈值用innodb_max_dirty_pages_pct控制,默认为75%)
- Master thread 工作方式
工作状态如下:
- loop
- backgroud(书中有错字)
- flush
- suspend
loop:
包含两种模式:每秒,10秒。
- 每秒操作:
1 redo日志缓冲刷写到日志,包括尚未提交事务(大事务提交快的原理)。
2 合并插入缓冲(可能)。
3 最多刷新100个缓存池脏页(可能)。
ps:5.6及以后将master中刷脏操作移到page cleaner thread中。并且刷页数量innodb_io_capacity控制并判断(不再是100个硬编码,而是5%innodb_io_capacity,小于则写入),并且通过innodb_max_dirty_pages_pct控制刷新界限为75%。5.6以以后版本通过自适应刷写,通过特定算法低于75%脏页时也动态刷脏。
- 当前没有任何活动,线程切换到background。
- 每10秒操作:
1 刷新100个缓存池脏页。(可能)
ps:5.6以后刷脏操作移到page cleaner thread中。并且刷页数量innodb_io_capacity控制并判断(100%innodb_io_capacity,小于则写入)。
2 合并最多5个脏页
ps:5.6以后通过innodb_io_capacity控制,5%为阈值。不同于每秒,10秒循环中必定执行。
3 日志缓冲刷写到日志。
4 删除无用的undo页(full purge操作)。
ps:5.6后通过onnodb_purge_batch_size控制最大回收undo页数。
刷新100个缓存池脏页。5.6后超过70%脏页比,刷新所有脏页。低于则刷新10%innodb_io_capacity。
background:
1 删除undo页
2 合并插入缓冲
3 跳到主循环
4 跳转到flush loop中。
flush loop:
判定脏页比是否大于70%,并执行相关刷脏机制。
- Innodb特性:
- 插入缓冲
参考资料:
insert buffer FAQ
About change buffer from mysql website.
The analysis of source code for mysql 5.6 change buffer .
Ps: Insert buffer也是一个持久化的对象,虽然它也存在于缓冲池中。利用刷脏机制持久化于磁盘中。
早期利用insert buffer作为非聚簇索引在索引不唯一的情况下,缓存非聚簇索引的插入更新和插入操作。索引不唯一保证了,数据库无需先查找索引页在进行插入,减少了随机读写。5.6及以后利用了chage buffer代替insert buffer,并且支持delete操作。
inserte buffer实际还是一颗b+树,并且是一个全局唯一树。非叶子节点有search key(4字节space表空间id(每一个表id都唯一),marker1字节,offset表示页偏移量。),叶子节除search key后接metadata和插入记录。配合使用insert buffer bitmap实现merge管理。
触发insert buff merge的情况:
1 辅助索引页被读取到缓冲池(select 时先查insert buffer bitmap,若查找的数据页存在于insert buffer树中触发合并)。
2 master thread循环执行merge,merge的页选定为随机选定。
3 insert buffer中辅助索引插入后,该页可用空间小于 1/32,执行merge。
- 两次写(保证页写入部分失效下正常恢复)
- 自适应哈希
- 异步io配合刷新临近页
第三章文件
- Mysql中的参数类型:
- 动态参数
- 静态参数
静态参数一般需要停机更改,实例运行时为只读状态。
- Mysql中的日志:
日志文件:
错误日志(error log)
二进制日志(binlog)
记录除select、show以外的语句对数据库执行的操作。每一个事务都拥有单独的缓存,当事务过大内存无法放入,将放入临时文件。事务提交时,将缓存内容写入二进制文件中。
作用如下:
- 数据恢复。
- 复制。
- 审计
binlog格式:
- statement(语句)
- row(行更改)
- mixed(混合)
- 慢查询日志(slow query log)
5.6.5后除慢查询外,增加未使用索引语句记录。5.1后可使用slow_log表(需要更改log_output为table)进行检索。慢查询中还引入了逻辑读取和物理读取,还能指定捕获超过一定逻辑读取次数的语句。
- 查询日志(log)
记录了所有请求信息,包括语句是否执行。
套接字文件:
用于socket连接。pid文件:
保证只能启动唯一一个mysql主程序,后缀为 .pid表结构定义文件:
后缀为 .frm文件。表空间文件:
可以由多个文件组成一个表空间,通过不同文件放入不同的磁盘中,以提高性能。针对不同的表可以设置独立的表空间,独立表空间文件后缀为 .ibd。独立表空间仅仅存储表的数据,索引,插入缓冲bitmap等信息。其余信息如:undo(5.5后可以调整为独立空间)log、插入缓冲、系统事务、二次写缓冲等仍然存放于共享表空间中。重做日志(redo log):
一般文件名字类似为ib_logfileXXX。会有多个文件,以类似循环写入的方式进行记录数据。重做日志过小会导致频繁的磁盘读写,过大导致异常恢复时间过长。不同于binlog,binlog仅仅在事务提交时执行一次写入,redo无论事务是否提交,都会不断写入,这也是事务能够快速提交的原因。注意,redo log先写入 redo log buff 然后再写入redo log file。
redo log是持久性的保证。为了实现redo log 的同步刷新,还必须将innodb_flush_log_at_trx_commit调整为1。(0代表1秒一次同步写;1代表事务commit时同步写;2代表异步写;)
第四章 表
- innodb引擎中表都具有主键,并且数据根据主键组织。假如建表时没有显示指定索引,mysql将会按照以下方式选定主键:假如有非空唯一索引,则利用该索引;不符合上述情况,则自动创建一个6字节大小的指针作为主键。
- 表空间结构
表空间(共享表空间)物理文件为ibdataXXX。一般情况下,表空间会随着undo log的使用而增大。当undo空间被full purge时(GC),文件不会缩小,回收的空间会标记为可用空间以供下次使用。
表空间组成如下:- 段(segment)
段一般有数据段,索引段、回滚断等。数据段是b+树的叶子节点,索引端为b+树的非叶子节点。
- 区(extent)
区是由页组成的,每一个区大小为1M,按照默认页16k计算,一共由64个页组成一个区。
- 页(亦称块 page/block)
页默认大小为16k,可以调整。除了一般页外,还有压缩页。每一个段初始使用时,先使用32个碎片页,然后再申请64个连续页。
页类型如下:- 数据页
- undo页
- 系统页
- 事务数据页
- 插入缓冲位图页
- 插入缓冲空闲列表页
- 未压缩的二进制大对象页
- 压缩的二进制大对象页
- innodb是一个以行的方式进行存储数据。
- innodb行存储格式
Antelope文件格式如下:
- compact
compact行记录格式如下:
变长字段长度 NULL标志位 记录头信息 数据 1) 变长字段长度
变长列长度小于255使用一个字节表示,否则使用两个字节表示。单行变长最大长度为65535(实际刨去头开销等,小于该值。这也是为什么单表所有varchar字段类型总字节不能超过65535的原因,会引起报错,需要使用blob,text等字段类型。)
2) NULL标志位
NULL标志位表明该行数据中是否含有NULL值,占用一个字节,有则用1表示那一列数据存在null值(011代表第一,第二列)。
3)记录头信息
占用5字节,包括标识该行是否删除、记录类型(叶子节点等)、页中的下一条记录的相对位置等。
- 数据部分除了正常的列数据以外,还包括了两列隐藏列:事务ID列、回滚指针列,放在真实数据前。 当使用定长char数据类型时,字段未能占用所有空间时,利用0X20占用空间。
- redundant
Barracuda文件格式:
Barracuda不同与Antelope,存放的blob数据类型全部采用行溢出的形式存放,数据页中存放的是指针。
- Compressed
利用zlib进行压缩。 - Dynamic
行溢出数据
为什么会存在行溢出?
页大小为16k即16384b大小,而行最大为65536,所以varchar,blob,test等数据类型都可能会导致行溢出。blob,text虽然是大对象。但是当他们小于一页范围内,不存在行溢出的问题。行溢出通过利用多个页进行解决。详细见官网
注意,页大小65536但是不意味着能够放入65536个字符串,能够放入的字符需要根据编码转换为对应的字节数占比计算得出。从MySQL5.x开始,char中表示的字符串数,不是字节数。-
Innodb数据页结构
Innodb数据页由以下部分组成:- File header (文件头,大小固定,38个字节)
组成部分按顺序如下:checksum,表空间中页偏移值,前向页,后向页,最后一次修改LSN号,存储页类型,flush_lsn(特殊用途),表空间ID。 - Page header(页头,大小固定,56字节)
记录了数据页的状态信息。包括:Slot(槽数),堆中第一个数据的指针,堆中记录数,指向可重用空间的首指针,已删除记录数,最后插入记录位置,最后掺入方向,修改当前页的最大事务ID,当前页在索引树的位置,索引ID标识所属索引等。 - Infimun 和Supermum Records
虚拟行记录,Inf比任意键值都小,Sup比所有键值大。 - User Records(用户行记录)
- Free Space(空闲位置)
- Page Directory(页目录)
存放了记录的相对位置(不是偏移量),是一个稀疏目录(一个目录对应多条记录,又被称为slot)。通过二叉查找,找到对应记录。 - File Trailer(页尾信息,大小固定,8字节)
由checksum和Lsn组成,并且与Fileheader中的checksum、Lsn对应。当两者不一致时,说明页出现残损。该办法保证了页的完整性。
- File header (文件头,大小固定,38个字节)
数据完整性
ps:关系模型中三大完整性为:1实体完整性(非空主键)。2参照完整性(外码)。3 用户自定义完整性。
完整性实现方式如下:
- Primary key(主键)
- Unique key
- Foreign key (外键)
- Default(列的数据类型,包括使用enum,set类型。)
- 触发器
- Not null
- 视图(view)
一般而言(mysql不支持物化视图,可以用真实表实现物化视图。),视图是一个虚表(视图中的数据并不真实存放,数据只是存放在构成视图的实表中)。通过更改视图,可以改变实表的值。
9.分区
以下言论未经证实,不负责。只是从理论猜想,未有能力进行测试。有机会会验证。
ps:对于读的影响: 分区后可能存在当一次查询需要多树查询结果合并时,本来一次树查询复杂度为log2xxN提高到 Mxlog2xxN(M为分区数))。更为致命的是本来是存放在相邻位置的数据再次被变为散列存储,引发随机读取。整体可能导致查询耗时上升。 因此,要想尽可能的提高单库,单磁盘下的性能,应该将单表分区,并且分区部署在不同磁盘上。(抛开坏处而言。分区不一定提高速度,但是可以提高系统上限。将读负载划分到不同磁盘上。)
对于写的影响:多盘分区后从理论上讲在redo log写上限到达前,能够提高页刷脏性能(将写负载划分到不同盘上)。分区后假如存在Null值,将会导致数据倾斜在左边。
Notice Notice注意,书上说Mysql innodb不能使用单表多磁盘。官网描述是Innodb会出现安全bug(通过指定directory越权访问其他表格)。(实测Winodws、Linux下5.7可以实现单表多磁盘。)
PS: 分区前需要确保Mysql有对应文件夹的linux权限。
CREATE TABLE users (
uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL DEFAULT '',
email VARCHAR(30) NOT NULL DEFAULT ''
)
engine='InnoDB'
PARTITION BY RANGE (uid) (
PARTITION p4 VALUES LESS THAN (3000000)
DATA DIRECTORY = '/data/data',
PARTITION p6 VALUES LESS THAN (6000000)
DATA DIRECTORY = '/data/data2'
);
Mysql中分区类型:
- Range分区
连续区间分区。 - List分区
离散区间分区。 - Hash分区
自需要指定分区的值和分区数,并且利用hash函数进行划分分区。 - Key分区
类似Hash分区,不同的是利用数据库内置划分函数进行分区。 - Columns分区
以上四种分区都需要指定分区类型为整型(非整型需要转换为整型),Columns就是可以直接利用非整型数据进行分区。
- 子分区
分区的基础再分区,mysql可以在Range和List基础上,进行Hash和Key分区。
第五章 索引与算法
- 索引类型
- B+树(聚簇索引,非聚簇索引)
聚簇索引物理上页并不是一定是连续分布的。非聚簇索引在innodb都需要回表查找。
B+树是一种平衡树,叶子节点都是有序数据,通过指针连接数据页(叶子节点)。非叶子节点为索引页。
为了提高空闲数页的利用率,内部一般不会马上进行数据页的拆分。一般会通过移动数据页内的数据,以提高利用数据页内的空闲空间。实际上中间节点的选择会考虑页的page header的的过去参入情况进行选择中间节点,以进一步合理的使用页。
B+树插入方式(考虑插入时,各种页面是否以满,对应不同的插入方式。)
叶子节点(数据页) 非叶子节点(索引页) 插入方式 未满 未满 直接将数据插入到数据页中 以满 未满 1) 拆分叶子节点,将叶子节点中间节点放入到索引页中。 2)小于中间节点的放入左边叶子节点,大于等于的记录放入右边叶子节点。 已满 已满 1) 拆分叶子节点,将叶子节点中间节点放入到索引页中。 2)小于中间节点的放入左边叶子节点,大于等于的记录放入右边叶子节点。3)类似数据页拆分的方式进行拆分。唯一不同的是,将索引页的中间节点建立为新的节点,然后中间节点的左右数据作为该节点的左右孩子。 B+树删除操作
填充率=页占用数/页总空间数
叶子节点小于填充率 非叶子节点小于填充率 删除方式 No No 直接删除该条记录,假如改记录为index中的节点,将改节点的右节点作为索引节点代替。 Yes No 合并叶子节点和它兄弟节点,同时更新index中的节点。 Yes Yes 1)合并叶子节点和它兄弟节点。2)更新index中的节点。3)合并index和它兄弟节点。
- 全文索引
- 哈希索引
- 索引选择参照值Cardinality
Cardinality = 索引值条数/总记录数(当索引值唯一时候该值为1,否者小于1。越解决1,越能说明索引的区分度,一定程度上索引值建立比较合理。) - 索引的特性
- 联合索引(前缀覆盖原则)
- 索引结果合并(假如有a,b列索引。当数据扫描时,将会利用a,b列结果合取交集取出对应的回表主键值。)
- 范围扫描容易导致解析器不使用索引(哪怕是索引范围覆盖在非聚簇索引中,可以通过FORCE INDEX 强制使用索引。USE index命令并不会强制使用index)。
- MRR优化(开启后会自动将上述范围问题转化为键值查找问题,以使用索引。但是经过实测,MRR在一定范围内有效,当扫描范围过大,MRR会失效。)
- ICP(查找条件下放到索引中,减少无关数据页的调入到内存中。)
- 自适应哈希算法(数据库内部实现,无法控制。将多次访问键值语句以哈希的形式进行查找。)
- MRR
MRR能够优化range,ref,eq_ref类型的查询。
MRR通过将主键排序以提高性能。减少了缓冲池页面的替换,批量处理对键值的查询(将范围查询变为键值查询)。
第六章 锁
- lock与latch区别
lock | latch | |
---|---|---|
对象 | 事务 | 线程 |
保护 | 数据库内容 | 内存数据结构 |
持续时间 | 整个事务过程 | 临界资源 |
模式 | 行锁、表锁、意向锁 | 读写锁、互斥量 |
死锁 | wait for graph、time out机制进行死锁检测与处理 | 无死锁检测与处理机制。仅通过加锁顺序保证无死锁现象 |
存在于 | Lock Manager的哈希表 | 每个数据结构的对象 |
- innodb锁类型
- 表级
意向共享锁
意向排他锁
实际实现中,一般情况下意向锁不会阻塞除全表扫描以外的任何请求。
- 行级
共享锁
排他锁
- 查看锁信息
通过information_schema下的表innodb_trx、innodb_locks、innodb_lock_waits查询可以得知锁相关信息
- MVCC
- 一般情况下(rr等级及以下隔离等级),Select没有显示声明则没有任何锁,锁也不会影响select的读。如果需要select中加锁,S锁则用select lock in share mode;X锁select for update。
- 自增长与锁
Innodb内部实现的自增长,原始是通过表锁实现,通过语句粒度进行申请,而不是事务粒度进行申请以保证性能。由于原始实现并发性能差,5.1版本后通过互斥量实现更轻量级自增实现,并且按照插入类型不同提供不同方式的自增支持(innodb_autoinc_lock_mode调整自增策略)。
对于自增列,必须是索引且是索引第一列。
- 外键与锁
外键父表的插入、删除、更改会导致子表的相关外键插入、删除、更改被阻塞(锁引起)。原因是每一次子表的相关操作会以S锁的形式对父表进行Select以实现外键约束,后续才进行相关的插入、删除、更改操作。
- innodb 行锁实现方式
- Record Lock
锁定记录本身。
- Gap Lock
锁定一个范围,不包括记录本身。
- Next-key Lock
锁定一个范围,并包含记录本身。
PS PS PS以下讨论环境为REPEATABLE READ, READ COMMITTED锁的范围会远小于REPEATABLE READ.
只会对update,select for update ,select for lock in share mode, delete产生next key lock 和gap lock;insert 是record lock ;
对于被范围锁定的语句来说,锁定的对象为where后的条件。Update会锁定扫描过的列,因此当条件中没有索引列,可能会引起锁表(可以通过explain 大致查看锁定范围,锁定对象)。当where条件中中包含非索引列和索引列,锁定只考虑索引列。
除了扫描的键值是索引是唯一索引(或者是主键),才能将Gap lock 和Next key lock变为Record Lock。多个index可选时候,可以通过explain查看sql选定的index。
官网关于锁定详解
测试案例如下
#测试数据如下
![6470c763acb04507ec05f54f19da989d.jpeg](en-resource://database/784:0)
# a, b
10, 0
20, 2
30, 5
40, 7
50, 10
#建表语句
CREATE TABLE `lock_t` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
KEY `l_1` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#session A
begin;
select * from mytest.lock_t;
update mytest.lock_t set a=21 where a=20;
#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(16,1);
#fail
insert into mytest.lock_t values(9,1);
#successful
insert into mytest.lock_t values(10,1);
# fail
insert into mytest.lock_t values(30,1);
# successful
==============================
#建表语句
CREATE TABLE `lock_t` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
KEY `l_1` (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#session A
update mytest.lock_t set a=23 where b= 2;
#session B
insert into mytest.lock_t values(999,11);
fail
insert into mytest.lock_t values(46,6);
fail
insert into mytest.lock_t values(26,6);
fail
--------------------------------------------
rollback all session
---------------------------------------------
#session A
begin;
select * from mytest.lock_t;
update mytest.lock_t set a=25 where a= 20;
#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(30,2)
successful
insert into mytest.lock_t values(29,2);
fail
insert into mytest.lock_t values(10,2);
fail
insert into mytest.lock_t values(9,2);
successful
锁定的对象为where 后所接的条件,例子如下
#建表语句
CREATE TABLE `lock_t` (
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
KEY `l_1` (`a`),
KEY `l_2` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
#数据同上一例子
#session A
begin;
select * from mytest.lock_t;
update mytest.lock_t set b=1 where a= 30;
#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(35,2);
fail
insert into mytest.lock_t values(35,1);
fail
insert into mytest.lock_t values(45,1);
successful
利用以上例子还能发现mysql锁定方式为[xxx,bbb)的锁定方式,比如update set a=30 ...则锁定[20,30)的范围内的a值。
- sql 并发问题
- 脏读
- 不可重复读
- 丢失更新
- 幻读
- 锁问题
- 阻塞
- 死锁
第七章 事务
- 数据库特性
- A 原子性
- C 一致性
- I 隔离性
- D 持久性
- 事务的类型
- 扁平事务
- 带保存点的扁平事务
- 链事务
- 嵌套事务
- 分布式事务
- 事务的实现
- redo log
redo log由redo log buffer和redo log file组成。由于redo log最小单位为512个字节,因此无需double write保证同步写入的正确性。每一个redo log file都是通过循环顺序写的方式写入,每一次写入除了顺序写部分,还需要写入log file header 、checkpoint1 、null(占位)、checkpoint2(同checkpoint1,保证物理介质异常下,依然能够正确读取出checkpoint信息。) Redo log记录的是页的物理日志,因此其在ROW日志格式下具有幂等性。
- undo log
MVCC的具体实现方式,通过undo log记录数据的历史版本实现了MVCC和Rollback功能。undo log是逻辑日志,因此其需要double write 技术保证写入的数据完整。undo log也需要redo log同步写入,以保证数据不丢失。undo log决定了事务并发的理论上限。5.6开始可以通过指定独立空间以提升性能。
undo log的资源回收依靠purge 线程进行处理。为了提供空间利用率,undo 页可以重用。undo log只有insert 和update(负责update和delete操作)两种类型。
- LSN
log sequence number,其具有checkpoint位置,页版本和重做日志写入总量的作用。通过写入字节的累加,实现LSN号的变化。
数据库恢复时,通过对比重做日志的LSN号和对应页的LSN号,得知页面是否需要从左,假如某一页P的LSN号大于等于该页redo log中的LSN号,则无需重做。
- Group commit
为了进一步提供性能,innodb引入了redo log 的group commit功能,将多次同步写合并为一次同步写。 - 事务执行步骤
- 没有binlog情况下:
1) 修改事务的内存信息,并且将信息写入Redo log buffer(包括redo信息,undo信息),写入undo log等。
2) 调用同步写,确保redo log从缓冲区写入到磁盘中。
3) 事务提交,完成一个事务。
- 具有binlog的情况下:
binlog引入必须考虑存储引擎层写入和binlog写入顺序一致。
1) 事务提交到innnodb引擎,进行prepare工作。
2) Mysql上层写入binlog。
3) innodb存储引擎层将日志写入重做日志(重复没有binlog 的步骤)。
为了保证binlog和redo log顺序一致,引入prepare_commit_mutex锁。但是引入后导致group commit失效。
- 5.6及以后解决方案
BLGC
BLGC的核心是引入有序队列。每一个事务从flush到内存,Sync阶段(binlog写入),commit阶段(存储引擎写入)。事务的写入顺序按照第一阶段的写入顺序写入。
- 事务提交的方式
- 显式提交
- 隐式提交
- 分布式事务
- 长事务(大事务)
核心思想就是锁定插入或者更改范围,然后通过切糕的方式小部分写入。