存储引擎即表的类型,决定了底层文件系统中文件的相关物理结构。
InnoDB
:支持外键、事务的存储引擎5.5
版本后默认的存储引擎。InnoDB
InnoDB
表名.frm
存储表结构(MySQL8.0
后,合并在表名.ibd
中)表名.ibd
存储数据和索引InnoDB
是为处理巨大数据量代最大性能设计,数据量大,并发大。支持行锁,粒度小。MyISAM
,InnoDB
写的处理效率差一些,并且会占用更多的磁盘空间以保存数据和索引。InnoDB
不仅缓存索引还要缓存真实数据, 对内存要求较高 ,而且内存大小对性能有决定性的影响。MyISAM
:主要的非事务存储引擎MyISAM
提供了大量的特性,包括全文搜索、压缩、空间函数(GIS)等count(*)
效率高表名.frm
存储表结构表名.myd
存储数据(mydata
)表名.myi
存储索引(myindex
)Archive
:归档,用于数据存档zlib
压缩库Blackhole
:丢弃写操作,读操作返回空Blackhole
表的日志,所以可以用于复制数据到备库,或者只是简单地记录到日志。CSV
:存储数据以逗号分隔NOT NULL
Memory
:置于内存的表Memory
采用的逻辑介质是内存,响应速度非常快,但是重启后数据会丢失,没有持久化。比MyISAM
快至少一个数量级。要求存储的数据是数据长度不变的格式。
Hash
索引和B+Tree
索引,默认使用Hash
索引,等值查找速度非常快,但是范围查找较慢Memory
表的大小是受限制的,基于max_rows
和max_heap_table_size
参数。Memory
存储引擎的表实际对应一个磁盘文件,该文件名与表名相同,类型为frm
,该文件中只存表结构,而数据文件都是存储在内存中。所以服务重启后,表结构还会保留但数据丢失。使用场景
Federated
:访问远程表是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一个很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。
Merge
:管理多个MyISAM
表构成的表集合Merge
表是由多个MyISAM
表合并而来的虚拟表。
NDB
:MySQL
集群专用存储引擎特点 | MyISAM |
InnoDB |
Memory |
Merge |
NDB |
---|---|---|---|---|---|
存储限制 | 有 | 64TB | 有 | 没有 | 有 |
事务 | 支持 | ||||
锁机制 | 表锁 | 行锁 | 表锁 | 表锁 | 行锁 |
BTree |
支持 | 支持 | 支持 | 支持 | 支持 |
Hash 索引 |
支持 | 支持 | |||
全文索引 | 支持 | ||||
聚簇索引 | 支持 | ||||
数据缓存 | 支持 | 支持 | 支持 | ||
索引缓存 | 只缓存索引,不缓存数据 | 数据、索引都缓存 | 支持 | 支持 | 支持 |
数据可压缩 | 支持 | ||||
空间使用 | 低 | 高 | 低 | 低 | |
内存使用 | 低 | 高 | 中等 | 低 | 高 |
批量插入的速度 | 高 | 低 | 高 | 高 | 高 |
外键 | 支持 |
MyISAM
和InnoDB
InnoDB
提供了良好的事务管理崩溃修复能力和并发控制,对于要求事务完整性的场合需要使用InnoDB
,缺点是读写效率低,内存占用相对较大。支持行锁,适合高并发操作
对于MyISAM
,如果是小型应用,系统以读、插入为主,很少更新、删除操作,并且对失误要求没有这么高,可以选择。占用空间小,处理速度快。不支持事务。表锁,不适合高并发操作。
InnoDB
行格式默认16KB
名称 | 占用大小 | 说明 |
---|---|---|
File Header |
38字节 | 文件头,描述页的信息 |
Page Header |
56字节 | 页头,页的状态信息 |
Infimum 和Supremum |
26字节 | 最大和最小记录,这是两个虚拟的行记录 |
User Records |
不确定 | 用户记录,存储行记录内容 |
Free Space |
不确定 | 空闲记录,页中还没有被使用的空间 |
Page Directory |
不确定 | 页目录,存储用户记录的相对位置 |
File Trailer |
8字节 | 文件尾,校验页是否完整 |
FileHeader
描述各种页的通用信息
Fil_page_offset
,页号,InnoDB
通过页号可以唯一定位一个页
Fil_page_type
,页的类型
File_page_prev
和Fil_page_next
,上一页,下一页的页号,通过双向链表连接各页,实现不需要物理连接,而是逻辑连接
Fil_page_space_or_chksum
,校验和,通过与文件尾的校验和比对,判断页在磁盘IO
时是否出现异常
Fil_page_lsn
,Log Sequence Number
,页面被最后修改时对应的日志序列位置
File Trailer
包含校验和和LSN,为了校验页的完整性
Free Space
页内存储记录的部分的剩余空间
User Records
记录按照指定的行格式存放在这里,形成单链表
Infimum
和Supremum
最大记录和最小记录,heap_no
分别对应0
和1
,record_type
分别对应2
和3
Page Directory
用来存储每组最后一条记录的地址偏移量。这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称为slot
槽,每个槽想到与指向了不同组的最后一个记录。通过二分法找到具体的组(由于页目录记录的是组中最大记录,而记录是单向链表,所以需找到上一组的槽,然后往后找记录),再到组中查找数据。
每个组中最后一条记录的头部信息中,会存储改组一共有多少条记录,作为n_owned
Page Header
InnoDB
行格式(记录格式)Compact
行格式在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表。这个列表与字段顺序是相反的。
Compact行格式会把可以为NULL的列统一管理起来,存在一个标记为NULL值列表中。如果表中没有允许存储 NULL 的列,则 NULL值列表也不存在了。同样是与字段顺序相反,跳过not null
字段。
1
,表示该值为NULL
0
,表示该值不为NULL
delete_mask
,标记当前记录是否被删除
如果真实删除记录,其他记录需要重新排列,导致性能消耗。
min_rec_mask
,非叶子节点的最小记录都会添加该标记,值为1
record_type
,这个属性表示当前记录的类型,一共有4种类型的记录
0
,普通记录1
,非叶子节点记录2
,最小记录3
,最大记录heap_no
,表示当前记录在本页的位置,最小记录为0,最大记录为1
n_owned
,页目录中每个组最后一条记录的头信息中会存储该组一共有多少条记录
next_record
,表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量
除了记录真实字段的值外,MySQL还维护了3个隐藏字段
row_id
,行ID,唯一标识一条记录,当表结构没有定义主键时,该字段作为隐藏主键存在transaction_id
,事务id,当前记录版本的修改事务ID。详见MVCCroll_pointer
,回滚指针,记录了该记录的历史版本的指针列表。详见MVCC一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65533个字节,这样就可能出现一个页存放不了一条记录,这种现象称为行溢出。
在Compact
和Reduntant
行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中进行分页存储,然后记录的真实数据处用20个字节存储指向这些页的地址
Dynamic
和Compressed
行格式Compressed
和Dynamic
两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。如图,在数据页中只存放20个字节的指针(溢出页的地址),实际的数据都存放在Off Page
(溢出页)中。
Compressed
行记录格式的另一个功能就是,存储在其中的行数据会以zlib
的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储。
Redundant
行格式注意Compact
行格式的开头是变长字段长度列表,而Redundant
行格式的开头是字段长度偏移列表,与变长字段长度列表有两处不同:
少了“变长”两个字:Redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度偏移列表。
多了“偏移”两个字:这意味着计算列值长度的方式不像Compact行格式那么直观,它是采用两个相邻数值的差值来计算各个列值的长度。
索引(Index
)是帮助MySQL
高效获取数据的数据结构 ,创建索引的目的是减少磁盘I\O
次数。
索引是在存储引擎中实现的,每种存储引擎的索引不完全相同。
优点
IO
成本。缺点
页是磁盘与内存交互的基本单位。B+Tree的节点是页。
根据叶子节点存储的内容,可以区分为聚簇索引和二级索引。
B+Tree
可以分层很多层,规定叶子节点所在层为第0层,是存放用户记录的层。往上1-3
层为目录页。
一般情况下,用到的B+树都不会超过4层,可以假设叶子节点存放的用户记录有100条,而目录页存放的记录可以达到1000条,那么:
100
条记录100*1000=100000
条记录100*1000*1000=100000000
条记录100*1000*1000*1000=100000000000
条记录4层已经可以存放足够多的记录了。
同一层页与页之间用双向链表连接,页内的记录用单向链表连接。
在非记录页节点中,存放内容为索引列+页号,记录页中,存放内容为记录值(根据聚簇索引和二级索引不同而不同)
聚簇索引是基于主键(如果表结构没有声明主键,则会使用行格式中的隐藏字段row_id
作为默认主键)建立的索引。
聚簇索引并不是单独的索引,而是一种数据存储方式,数据即索引,索引即数据。
特点
优点
缺点
InnoDB
引擎,一般定义一个自增的ID列作为主键限制
InnoDB
引擎支持聚簇索引row_id
)建立的。对于非主键字段建立的索引(包括联合索引)都将采用二级索引。与聚簇索引不同的是,在二级索引中,叶子节点存放的不再是完整的记录,而是建立索引的列+主键
。
因为存放的内容仅包含索引的列和主键,所以在使用二级索引查询数据时,常常还需要进行回表操作,即根据索引查询到主键后,再根据主键到聚簇索引中查询相关的字段。
索引覆盖即二级索引已经包含了需要查询的所有字段,不需要再进行回表。
InnoDB
中B+树索引的注意事项需要保证B+树在同一层内节点的目录记录除了页号外,是唯一的。
聚簇索引索引列为主键,是必定唯一的。
而非唯一索引的二级索引是通过记录索引列的值+主键来保证除页号外记录唯一。
MyISAM
引擎的索引方案MyISAM
默认使用B+Tree
作为索引结构,本质为二级索引,MyISAM
不支持聚簇索引。叶子节点存放的是数据记录的地址。
MyISAM
引擎底层文件系统中,数据和索引是分开存放的(myd
和myi
文件)
MyISAM
存储记录时,按照记录的插入顺序存储,不划分数据页,由于没有刻意按照主键大小排序,所以无法使用二分法查找记录MyISAM
会将索引存放在索引文件中,叶子节点记录的是索引列的值+数据记录地址
MyISAM
和InnoDB
对比MyISAM
的索引方式都是“非聚簇”的,与InnoDB
包含1个聚簇索引是不同的。小结两种引擎中索引的区别:
InnoDB
存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM
中却需要进行一次回表操作,意味着MyISAM
中建立的索引相当于全部都是 二级索引 。InnoDB
的数据文件本身就是索引文件,而MyISAM
索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。InnoDB
的非聚簇索引data域存储相应记录主键的值 ,而MyISAM
索引记录的是地址 。换句话说,InnoDB
的所有非聚簇索引都引用主键作为data域。MyISAM
的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB
是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。InnoDB
要求表必须有主键(MyISAM
可以没有)。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB
表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。空间上的代价
每创建一个索引,都需要创建一棵B+树,数据量越大,索引就越大。
时间上的代价
在更新、插入数据时,可能需要额外的时间进行记录移位、页面分裂、页面回收等操作维护索引树。
Hash
索引Hash
索引对于等值查询来说效率非常高,但是对于范围查找和排序效率非常低。
一个M阶的B树(M>2)特性
[2,M]
k-1
个关键字和k
个子节点,子节点的个数=关键字的数量+1
,k
取值范围[ceil(M/2),M]
k-1
个关键字,k
取值范围[ceil(M/2),M]
B树与B+树区别:
子节点数量=关键字数量+1
,Group By
和order by
的字段建立索引distinct
字段建立索引is not null
索引失效like
以%
开头索引失效or
前后存在非索引的列,索引失效目的:减少回表次数
虽然部分条件可能导致索引失效,但联合索引中包含该字段,可以在二级索引将条件排查后,缩小范围再回表。
Explain
table
这一列显示了对应行正在访问哪个表。每一条记录对应一个单表,JOIN
涉及(被)驱动表,或偶有临时表。
不论查询语句有多复杂,里面包含了多少个表,到最后也是需要对每个表进行单表访问的,所以MySQL规定,Explain
语句输出的每条记录都对应着某个单表的访问,该条记录的table
列代表着该表的表名。
id
在一个大查询语句中,每个select
对应一个唯一的id
id
如果相同,可以认为是一组,从上往下顺序执行id
值越大,优先级越高,越先执行id
号每个号码,表示一趟独立的查询,一个sql
的查询趟数越少越好select_type
这一行显示了对应行是简单还是复杂select
。
simple
值意味着查询不包括子查询和union
。如果查询有任何复杂的子部分,那么最外层部分标记为primary
,其余部分标记如下
subquery
包含在select
列表中的子查询中的select
derived
包含在from
子句的子查询的select
,会递归执行并将结果放到一个临时表中,称为派生表
union
在union
中第二个和随后的select
union result
union
的临时结果集
dependent
意味着select
依赖于外层查询中发现的数据。
名称 | 描述 |
---|---|
Simple |
Simple select(not using UNION or subqueries) |
Primary |
Outermost select |
Union |
Second or later select statement in a union |
Union result |
result of a union |
Subquery |
First select in subquery |
Dependent subquery |
First select in subquery, dependent on outer query |
Dependent union |
Second or later select statement in a union, dependent on outer query |
Derived |
Derived table |
Materialized |
Materialized subquery |
Uncacheable subquery |
A sub query for which the result cannot be cached and must be re-evaluated for each row of the outer query |
Uncacheable union |
The second or later select in a union that belongs to an uncacheable subquery |
partitions
匹配的分区信息
type
表示了MySQL决定使用什么方式访问表中的行。
system
当表中只有一条记录且使用统计数据是精确的的存储引擎时
const
根据主键或唯一索引与常量等值匹配时
eq_ref
连接查询时,被驱动表通过主键或唯一索引匹配
ref
普通二级索引与常数等值匹配
fulltext
全文索引
ref_or_null
意味着MySQL在ref
初次查找的结果里进行第二次查找以找出NULL
条目
index_merge
合并多个索引
unique_subquery
index_subquery
range
范围查找
index
当可以使用索引覆盖,但仍然需要全表扫描时
all
全表扫描
possible_keys
显示了查询可能使用哪些索引,这是基于查询访问的列和使用的比较操作符来判断的。这个列表是在优化过程的早期创建的,因此有些罗列出来的索引可能对于后续优化过程是没用的。
key
显示了MySQL决定采用那个索引来优化对该表的访问。如果该索引没有出现在possible_keys
中,那么MySQL选用它是出于另外的原因——例如,它可能选择了一个覆盖索引,哪怕没有where
子句。
key_len
显示了MySQL在索引里使用的字节数。主要针对联合索引。
ref
这一列显示了之前的表在key
列记录的索引中查找值所用的列或常数。
rows
是MySQL估计为了找到所需的行而需要扫描的行数
filtered
表示针对表里符合某个条件的记录数的百分比所作出的一个悲观估算。
extra
包含一些额外信息
Impossible where
where
永远为false
using index
使用了覆盖索引,不需要回表
using where
MySQL服务器将在存储引擎检索行后再进行过滤。
确保每一个字段的值都具有原子性,是不可再次拆分的最小数据单元。
在满足第一范式的基础上,还要满足表中每一行数据都是可唯一标识的,而且所有非主键字段都必须完全依赖主键,不能只依赖一部分。
在满足第二范式的基础上,确保每一个非主键字段与主键字段直接关联,不能依赖其他非主键字段,非主键之间相互独立。
遵循业务优先规则,适当增加冗余字段,提高读性能。
事务是一组逻辑操作单元,使数据从一种状态变换到另一种状态。
ACID
特性原子性(atomicity
)
原子性是指事务是一个不可分隔的工作单位,要么全部成功提交,要么全部失败回滚。
一致性(consistency
)
一致性是指执行事务前后,数据从一个合法性状态变换到另一个合法性状态
合法性指的是业务上的合法性而非语法上。
隔离性(isolation
)
隔离性是指一个事务不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的事务之间互不干扰。
持久性(durability
)
持久性指一个事务一旦提交,它对数据库的修改是永久性的,接下来的其他操作不应该对其有任何影响。是通过事务日志保证的。
活动的(active
)
事务对应的数据库操作正在执行中
部分提交的(partially committed
)
事务的最后一个操作在内存中已完成,但并未刷新到磁盘。
失败的(failed
)
处在活动的或者部分提交的事务,遇到某些错误而无法继续进行的
终止的(aborted
)
失败的事务在回滚操作完成后
提交的(committed
)
处在部分提交的事务完成刷盘后
脏写(dirty write
)
事务A修改了尚未提交的事务B修改的数据
脏读(dirty read
)
事务A读取了已经被事务B修改但未提交的数据。
不可重复读(non-repeatable read
)
在同一个事务中多次读取同一个数据,由于中途被其他事务修改并提交,造成读取数据的前后不一致问题
幻读(phantom
)
在同一个事务中多次读取同一个数据,由于中途其他事务插入了一些行,导致数据前后不一致的问题。
首先,所有隔离级别都解决了脏写的问题
READ UNCOMMITTED
,读未提交,在这个隔离级别,所有事务都可以读取到其他事务修改未提交的值。未解决脏读、不可重复读、幻读问题。READ COMMITTED
,读已提交,在这个隔离级别,保证所有事务只能读取到其他事务已提交的改变。避免脏读,无法避免不可重复读、幻读问题。REPEATABLE READ
可重复读,保证事务A在读取一条数据后,即使其他事务修改并提交该数据,事务A再去读取还是原来的内容。避免脏读、不可重复度,并且在MySQL
的MVCC
中解决了幻读问题。SERIALIZABLE
在事务持续期间,禁止其他事务对该表执行插入、更新、删除操作。性能十分低下。开发多用户、数据库驱动的应用时,最大的一个难点是:一方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以一致的方式读取和修改数据。
锁是数据库系统区别与文件系统的一个关键特性。
MyISAM
支持表锁,而InnoDB
支持行级锁。
lock
区别latch
latch
一般称为闩锁,因为其要求锁定的时间必须非常短。
lock
的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般的lock
的对象仅在commit
或rollback
后进行释放。
共享锁:Share Lock, S Lock
,允许事务读数据,多个事务同时进行而不互相阻塞。
排他锁:Exclusive Lock, X Lock
,允许事务删除或者更新数据。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。
select ... for update
语句加X
锁时,会把所有扫描的行锁上,因此在MySQL
中使用悲观锁必须确保使用了索引,而不是全表扫描。
Intention Lock
)由于InnoDB
引擎支持多粒度锁,这种锁允许事务在行级上的锁与表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB
支持一种额外的锁方式,称为意向锁
。
若将上锁的对象看成一颗树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。
如果需要对页上的记录
R
进行上X
锁,那么分别需要对数据库A、表、页上意向锁IX
,最后对记录R上X
锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。比如,在对记录
R
加X
锁之前,已经有事务对表1进行了S
表锁,那么表1上已经存在S
锁,之后事务需要对记录R
在表1上加IX
,由于不兼容,所以该事务需要等待表锁操作完成。
InnoDB
引擎支持意向锁的设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型:
IS Lock
):事务想要获得一张表中某几行的共享锁。IX Lock
):事务想要获得一张表中某几行的排他锁。由于InnoDB
支持的是行级别的锁,因此意向锁不会阻塞除全表扫描以外的任何请求。兼容性如下图所示。
AUTO-INC Locking
)自增锁是针对插入自增长属性时的特殊的表级锁。为了提高插入的性能,锁不是在事务完成后才释放,而是在完成对自增长值插入的SQL语句后立即释放。事务必须等待前一个插入完成(不需要等待事务的完成)。
InnoDB
中的行锁Record Lock
)单个记录上的锁。记录锁分S型记录锁
和X型记录锁
。
Gap Lock
)锁定一个范围,但不包含记录本身。
MySQL
在Repeateable Read
隔离级别中,是可以解决幻读问题的。InnoDB
提出Gap Lock
来防止插入幻影记录。
通过锁定表中尚不存在的行范围,阻止其他事务在该范围插入新数据导致幻读问题出现。
Next-Key Lock
)记录锁 + 间隙锁,锁定一个范围,并且锁定记录本身。
从对待锁的态度可以分成乐观锁
和悲观锁
,这两种并不是锁,而是锁的设计思想。
悲观锁总是假设其他事务总会修改数据,所以每次拿数据时都会上锁,这样其他事务想要数据时就会阻塞。
悲观锁通过数据库自身的锁机制来实现,保证数据操作的排他性。
乐观锁认为对同一数据的并发修改并不总会发生,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有更新数据,不采取数据库自身的锁机制,而是通过程序来实现。
全局锁就是对整个数据库加锁。当需要让整个库处于只读状态时,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(增删改)、数据定义语句(建表、修改表结构等)和更新类事务的提交语句。典型场景:全库逻辑备份。
Flush tables with read lock
update/delete
长时间持有锁的SQL在事务前面。Repeatable Read
调整为Read Committed
,可以避免很多因gap锁
造成的死锁。多版本并发控制(Multiversion Concurrency Control
),通过数据行的多个版本管理来实现数据库的并发控制。为了查询一些正在被另一个事务更新的行,并且可以看到他修改之前的值。
MVCC
的实现依赖于数据行的隐藏字段、Undo Log
、ReadView
,且只在Read Committed
和Repeatable Read
隔离级别中有效。
trx_id
每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务ID赋值给trx_id
隐藏列roll_pointer
每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志
中,该隐藏字段相当于一个指针,通过它找到该记录修改前的信息。对该记录每次更新后,都会将旧值放到一条undo日志
中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer
属性连接成一个链表,称之为版本链
,
ReadView
ReadView
就是事务在使用MVCC
机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB
为每个事务构造了一个数组,用来记录并维护系统当前活跃(启动但未提交)事务的ID。
设计思路
Read Committed
和Repeatable Read
隔离级别中,必须保证读到已经提交了的事务修改的记录,所以核心问题是需要判断版本链中,哪个记录是当前事务可见的。
ReadView
主要包含4个比较重要的内容
creator_trx_id
,创建当前ReadView
的事务ID。只有对记录有修改操作时才会生成并分配事务ID,否则在只读事务中,事务ID默认为0。trx_ids
,表示在生成ReadView
时,当前系统中活跃的事务ID。up_limit_id
,活跃的事务ID中,最小的ID。low_limit_id
表示生成ReadView
时,系统应该分配给下一个事务的ID值,是系统中最大的事务ID。ReadView
规则trx_id
属性值与ReadView
中的creator_trx_id
一致,意味着当前事务正在访问自己修改的记录,所以该版本可以被当前事务访问。trx_id
属性值小于ReadView
中的up_limit_id
值,意味着生成该ReadView
时,该版本的事务已经提交。所以该版本可以被当前事务访问。trx_id
属性值大于或等于ReadView
中的low_limit_id
值,意味着生成该ReadView
时,该版本的事务尚未启动。所以该版本不可以被当前事务访问。trx_id
属性值在ReadView
的up_limit_id
和low_limit_id
之间,需要判断该版本trx_id
是否在trx_ids
列表中
ReadView
时,该版本所属事务尚未提交。所以该版本不可以被当前事务访问。ReadView
时,该版本所属事务已经提交。所以该版本可以被当前事务访问。需要注意的是,在Read Committed
隔离级别下,每次读取数据前都会生成一个ReadView
来确保每次读到的都是已提交的最新数据。而在Repeatable Read
隔离级别中,只在事务第一次SELECT
时会获取一次ReadView
来避免出现不可重复读
问题。
Repeatable Read
隔离级别下解决幻读问题由于Repeatable Read
隔离级别中,只在事务第一次SELECT
时会获取一次ReadView
,当其他事务在该事务启动后插入的记录,有可能出现两种情况:
ReadView
时已启动,即事务ID在trx_ids
列表中ReadView
时还未启动,即事务ID大于low_limit_id
以上两种情况在MVCC
规则中,都不允许事务访问该版本的记录。避免了幻读问题的发生。
Read Committed
隔离级别下,每次读取数据前都会生成一个ReadView
来确保每次读到的都是已提交的最新数据。Repeatable Read
隔离级别中,只在事务第一次SELECT
时会获取一次ReadView
,事务中之后的查询操作都重复使用这个ReadView
。通过MVCC
可以解决:
MVCC
可以让读写互相不阻塞,提升并发处理能力。MVCC
采用乐观锁的方式,读取数据时并不加锁,写操作也只锁定必要的行。slow query log
)默认不开启,通过配置long_query_time
来定义慢,这些语句会被记录到慢查询日志中,结合explain
进行分析。
redo log
)重做日志提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
由存储引擎生成,记录物理级别上的修改,比如:页xx
,偏移yy
,写入zz
。
InnoDB
存储引擎是以页为单位来管理存储空间的。在真正访问页面之前,需要把磁盘上的页缓存到内存的Buffer Pool
中,所有变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页以一定频率被刷入磁盘(checkpoint机制
),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样可以保证整体性能不会下降太快。
而checkpoint
并不是每次变更的时候就触发的,可能出现刚写完缓冲池服务器就宕机的情况,如果没有redo log
这段数据就会丢失。而基于事务的持久性特征,一旦事务提交,对数据库的影响应该永久保存。
但频繁刷盘会出现的问题:
修改量与刷盘工作量不成比例
InnoDB
以页为单位进行磁盘IO
,仅修改一个字节的内容也需要刷新整个页(默认16kb
)数据,不合适
随机IO刷新较慢,涉及页面可能不相邻
InnoDB
引擎的事务采用了WAL(Write-Ahead Logging)
技术,这种技术的思想就是先写日志,再写磁盘,只有日志写入成功,才算事务提交成功,当发生宕机且数据未刷新到磁盘时,可以通过redo log
来恢复,保证数据的持久性。
顺序写入磁盘的
事务执行过程中,redo log
不断记录
redo log
与bin log
的区别,redo log
是存储引擎层产生的,而bin log
是数据库层产生的。假设一个事务,对表做了10万行的记录插入,在这个过程中,一直不断的往redo log
顺序记录,直到事务提交,才会一次写入bin log
文件中。
redo
的组成redo log buffer
),保存在内存中,易丢失redo log
刷盘策略innodb_flush_log_at_trx_commit
参数来控制redo log
的刷盘策略
0
,表示每次事务提交时不刷盘,系统默认每隔1s进行一次同步,如果期间MySQL
挂了,可能丢失1s内数据。1
,表示每次事务提交时都进行刷盘操作,如果事务没有提交,所以日志丢了也不会有损失。2
,表示每次事务提交时只会把redo log buffer
内容写入文件系统缓存page cache
,不进行同步。由os
自己决定什么时间同步到磁盘。MySQL
服务没挂,数据就持久化了,如果宕机了,可能丢失1s的数据。undo log
)回滚日志用来保证事务的原子性
和一致性
。
由存储引擎生成,记录逻辑操作。在事务中更新数据的前置操作中写入undo log
。
undo log
的必要性事务需要保证原子性,如果出现错误或需要回滚,就需要undo log
来恢复到事务开始前的状态。
undo log
会产生redo log
,因为undo log
也需要持久性的保护。
回滚数据
MVCC
当用户读取一行记录室,若该记录已经被其他事务占用,当前事务可以通过undo
读取之前的行版本信息,以此实现非锁定读取。
general query log
)以文本文件记录用户的所有操作,包括启动、关闭MySQL服务,所有用户连接的起始时间、所有指令等。默认不开启。
当数据发生异常时,查看通用日志还原操作时的具体场景,可以帮助定位问题。
error log
)在MySQL
中错误日志是默认开启且无法被关闭的。
以文本形式记录MySQL
的服务器启动、停止运行的时间,以及系统启动、运行和停止过程中的诊断信息,包括错误、警告和提示等。
通过错误日志可以查看系统的运行状态,便于及时发现故障、修复故障。如果MySQL
服务出现异常,错误日志是发现问题、解决故障的首选。
bin log
)二进制日志文件,以事件形式记录了数据库所有执行的DDL
和DML
等数据库更新事件的语句,并保存在二进制文件中。
主要应用场景:
bin log
文件来恢复数据。master
将bin log
传递给slaves
来达到数据一致的目的。在事务执行的过程中,先把日志写入到binlog cache
中,事务提交时,再把binlog cache
写入到binlog
文件中。
write
和fsync
的时机,可以有参数sync_binlog
控制
0
,每次提交事务都write
,由系统自行判断什么时间fsync
1
,每次提交事务都write
且执行fsync
N
,每个事务提交都write
,但累计N
个事务后才fsync
binlog
和redo log
对别redo log
是物理日志
,记录在某页,偏移量多少的数据做了修改,属于InnoDB
引擎产生的。bin log
是逻辑日志,记录的语句是原始逻辑,属于MySQL Server
层。不同作用:
redo
让InnoDB
有了崩溃恢复的能力bin log
保证mysql
集群结构的数据一致性redo log
在事务执行过程中不断写入,而bin log
仅在事务提交时写入,两者写入时间不一致,可能导致主从机数据不一致的情况。
在MySQL
集群中,某事务在执行中,redo log
写入了数据,而bin log
没有写入,导致主机数据修改了,而从机根据bin log
内容同步数据,出现了数据不一致的问题。
InnoDB
使用两阶段提交来解决以上问题。
在写入bin log
前后都写入redo log
,判断处在commit
阶段且不存在对应的binlog
,就回滚事务。
statement
,基于SQL
语句的复制。记录修改数据的SQL
语句row
,基于行的复制。记录哪条数据被修改了,修改成什么样。mixed
,混合模式复制。一般语句使用statement
格式保存binlog
,而使用一些函数,statement
无法完成主从复制的操作时,则采用row
格式保存binlog
。relay log
)中继日志只在主从服务器架构中的从服务器上存在。目的是完成主从服务器的数据同步,保证了主从服务器的数据一致性。格式与bin log
相同,可以借助mysqlbinlog
工具进行查看。
恢复注意点
中继日志是包含从服务器名的。如果是通过重装操作系统来恢复系统服务,需要将服务器名修改为原服务器名,避免出现数据恢复异常的情况。
Slave
会从Master
读取binlog
来进行数据同步。
三个线程
在主从复制的过程中,由三个线程实现,一个主库线程,两个从库线程。
二进制日志转储线程
,是主库线程。当从库线程连接的时候,主库可以将二进制日志发送给从库,当主库读取事件的时候,会在binlog
上加锁,读取完成之后,再释放锁。
从库I\O线程
,会连接到主库,向主库发送请求更新binlog
,这时候从库的I\O
线程就可以读取到主库的二进制日志转储线程发送的binlog
更新部分,并且拷贝到本地的中继日志。
从库SQL线程,会读取从库中的中继日志,并且执行日志中的事件,将从库中的数据与主库保持同步。
Master
将写操作记录在binlog
中Slave
将Master
的binlog events
拷贝到中继日志中。Slave
重做中继日志中的事件,将改变应用到自己的数据库中。MySQL
复制是异步的且串行化的。Slave
只有一个Master
Slave
只能有一个唯一的服务器IDMaster
可以有多个Slave
MySQL数据库教程天花板,mysql安装到mysql高级,强!硬!_哔哩哔哩_bilibili
MySQL技术内幕 (豆瓣) (douban.com)
高性能MySQL(第3版) (豆瓣) (douban.com)