连接处理过程:
处理连接->查询缓存(开销大8.0弃用)->语法解析->查询优化->存储引擎->文件系统
字符集:
ASCII:收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符,用一个字节编码。
ISO 8859-1:共收录256个字符,在ASCII的基础上扩充了128个西欧字符,用一个字节编码。(mysql5.7以前默认)
GB2312:收录了汉字、拉丁字母、希腊字母等,收录汉字6763个,其他字符682个。该字符集同时兼容ASCII字符集,编码时采用变长方式,如字符在ASCII字符集中采用一字节编码,否则采用两字节编码。
GBK:对GB2312字符集进行扩充,编码方式兼容GB2312。
UTF-8:几乎收录了各个地区使用的字符,兼容ASCII字符集,采用变长编码方式,编码一个字符时需要1-4个字节。
Unicode:采用UTF-8、UTF-16、UTF-32几种编码方案,UTF-8时使用1-4字节编码一个字符,UTF-16时使用2或4个字节编码一个字符,UTF-32时使用4个字节编码一个字符。
MySQL中的utf8和utf8mb4:
utf8mb3:“阉割”过的utf-8字符集,使用1-3个字节表示一个字符。mysql中utf8时utf8mb3的别名。
utf8mb4:正宗的utf-8,使用1-4个字节表示一个字符。Mysql8.0的默认字符集。(emoji表情占用4个字节)
MySQL字符集比较规则:
服务器级别、数据库级别、表级别、列级别
优先级从低到高
MySQL发送请求到接收响应过程发生的字符集转换:
MySQL支持的存储引擎(9种):
存储引擎的4种推荐选择,
1. 如要提供提交、回滚、崩溃恢复能力的事务安全(ACID兼容)能力,并要求实现并发控制,推荐InnoDB。
2. 如数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率。
3. 如只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的MEMORY引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果。
4. 如只有INSERT和SELECT操作,可选择Archive,Archive支持高并发的插入操作,但本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive。
InnoDB:
是一个将数据存储在磁盘上的存储引擎,支持事务、行级锁机制和外键约束的功能。以页为单位在磁盘和内存之间交互数据,一个页默认大小为16kb。
行格式:
1、COMPACT:
变长字段列表:记录varchar(m)、varbinary(m)、text、blob等类型占用的字节数,逆序存放。如果当前字符集为变长编码,char(M)也存放在变长字段列表,但至少要占用M个字节(如采用utf8时,占用字节范围为m-3m),为了在下次更新时有可能直接在本列更新,避免空间碎片。
NULL值列表:记录哪些列数据为null,主键和not null修饰的列不算在其中,必须用整个字节的位表示,不足高位补0,逆序存放。(如:00000101)
记录头信息:固定5个字节,记录删除状态、当前记录类型、在页面堆中的相对位置、下一条记录的相对位置。
记录的真实数据:
隐藏列:row_id(主键,默认为用户设置的主键,如未设置选择一个不为null的unique列,还没有才默认生成一个)、trx_id(事务ID)、roll_pointer(回滚指针)、具体列数据。
MySQL5.0以前使用。
字段长度偏移列表:将记录中所有列(包括隐藏列)的长度信息按逆序记录,采用相邻两个偏移量的长度差值就算各个列的长度。
MySQL5.7默认,与COMPACT类似,对于溢出列的处理不同,不在本数据页记录数据,仅用20字节记录地址。
采用压缩算法对页面进行压缩,同样取数据需要解压缩。
列溢出(COMPACT、REDUDANT):一个页的大小为16KB(规定一个数据页至少存储2行数据,数据页和溢出页不同),如果列数据超过这个大小则在本页存储前768个字节的数据,其他数据分散存储在其他页面,用20个字节记录指向的数据页,页面间使用链表连接起来。
变长字段列表、NULL值列表逆序存放:因为next_record(当前记录的真实数据到下一条记录真实数据的距离)指向记录头信息和真实数据之间的位置,向左读取是记录头信息,向右读取是真实数据,可以使记录中靠前的字段和他们对应的字段长度信息在内存中距离更近,提高高速缓存命中率。
InnoDB数据页结构:
数据间按主键从小到大串联成一个单向链表(next_recored指向距离,可能为负数)。Infinum->.......->suprenum
每个数据页得File Header部分都有上一页和下一页的编号,所有数据页组成一个双向链表。
Page Directory(页目录):
按照页内数据的单向链表划分槽,infimum单独占一个槽,supremum内1-8个数据占一个槽,其余4-8个数据占用一个槽,当新增数据时,如槽内已达到数据上限,则新增一个槽进行拆分,一边4个,一边5个。槽内记录分组中最大记录的偏移量。
根据主键如何查找一条记录:
索引分类:
聚簇索引:根据主键值大小进行排序,目录页记录主键+页号,数据页存储了完整的信息。--索引即数据,数据即索引。
二级索引:按照某一列大小排序,列值相同时按主键排序(列+主键的联合索引),目录页记录列+主键+页号,数据页仅记录列+主键信息。
联合索引:按照多列排序,第一列相同时按第二列大小,目录页记录索引列+页号,数据也仅记录索引列+主键信息。
覆盖索引(不算一类):要查询的列均在二级索引内,不需回表。
索引的代价:
*的代价:
注:一个B+树索引的根节点创建后便不会移动(页号不变),初始作为数据页时如数据满,则赋值到一个新页面里,该页面仍为根节点(目录页)。
B树与B+树??
MySQL系统数据库:
Mysql:存储用户账号、权限信息、存储过程和事件的定义信息,运行过程产生的日志、帮助信息、时区信息等。
Information_scheme:mysql维护的其他数据库信息,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引等。
Performance_schema:mysql服务器运行过程中的状态信息,性能监控。
Sys:通过视图的方式把nformation_scheme、Performance_schema的信息结合起来,便于开发人员查看。
Buffer Pool:
一片连续的内存,用户缓存磁盘中的页,减少磁盘I/O,由控制块(记录页所属表空间编号、页号、缓冲页在buffer pool中的地址、链表节点信息等;与缓冲页一一对应)、缓冲页、碎片组成。默认大小128M可调节。
Free链表:
Buffer pool申请完内存空间后,划分成若干空闲的控制块、缓冲页,将这些控制块组成一个双向链表,由一个基节点(40字节,不占用buffer pool内存,指向头控制块、尾控制块,记录节点数量count)开始。需要加载页时从free链表通过控制块找一个缓冲页,将该控制块从链表上移除。
如何判断buffer pool中是否存在需要加载的页:
hash,根据表空间号+页号可以定位一个页,故根据空间号+页号生成key判断。
结构:key(空间号+页号),value(控制块地址)
Flush链表:
页面数据发生变化的控制块链表,结构与free链表一致。
LRU链表:(最少使用原则,核心:提高Buffer Pool命中率)
解决Buffer Pool空间不足时,决定移除哪些页。
按照常规新增或使用将控制块移动到头部,会因buffer pool的预读机制(顺序访问某个区的xx个(默认56个)页面触发异步加载下一个区的所有页面;连续加载某个区的13个页面,异步将这个区的所有页面加载(默认不开启))及全表扫描导致加载的页不一定使用,大量使用频率低的页同时加载,将使用频率高的页从缓冲区淘汰掉,降低Buffer Pool的命中率。
优化:
将LRU链接分为热数据、冷数据,默认比例为3/8,页面首次加载时放在冷数据区的头部,当下次访问(每去页面读一次数据都算访问一次)该页面的时间间隔与第一次访问的时间间隔大于设置时间(默认1s)时,将当前页的控制块移动到热数据区的头部,同时访问热数据区的前1/4时,控制块在链表的位置不移动。
刷新脏页到磁盘:
1、后台线程定时从LRU链表的尾部扫描页面,检测页面是否修改,修改则刷新到磁盘释放空间。
2、后台线程定时从flush链表刷新页面到磁盘。
3、资源紧张时,会检测LRU链表尾部的页面是否有更新,没更新则会释放;特别紧张时也会出现用户线程从flush链表刷新页面。
在多线程环境下,访问buffre pool的链表需要加锁,为了提高处理速度将buffer pool拆分为若干个小的buffer pool(buffer pool内存分配小于1GB时,无法拆分),同时为了在运行过程中调整buffer pool的大小,且不经过复制,将每个buffer pool再细分为chunk(默认一个128M),通过动态添加chunk调整大小。
特性:
1、原子性:操作要么不做要么都做,不存在中间状态。
2、隔离性:指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。
3、一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。(原子性、隔离性都是保证一致性的手段)
4、持久性:持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。
事物的状态:
1、活动的:事务对应的数据库操作正在执行。
2、部分提交的:事务的最后一个操作执行完成,但操作都在内存中并没有刷新到磁盘。
3、失败的:事务处于活动、部分提交状态时,遇到了某些错误无法继续执行。
4、中止的:事务执行半截变为失败状态,回滚到执行前的状态。
5、提交的:处于部分提交状态的事务将修改过的数据都刷新到磁盘后。
保存点:使用savepoint + 保存点名称在事务对应的数据库语句中打点,可以在rollback时指定回到特定的点,否则将回滚到事务开始时。
保证系统因崩溃而重启时恢复到奔溃前的状态。有物理层面(指明对哪个表空间的哪个页进行修改)、逻辑层面(调用事先准备好的函数,执行函数后方可恢复,如删除一系列记录)。
如何保证redo log的原子性(一次操作可能有多条日志,如何确保为一组完整的):
以组的形式记录日志,必须以一条类型为MLOG_MULTI_REC_END的日志结尾;type为1的日志表示操作只有这一条日志即可满足原子性(如更新MAX ROW ID,每次到256倍数时会更新页面7)。
redo log buffer:
为了解决记录redo log直接写入磁盘效率问题引入redo log buffer(一片连续的内存,由多个block组成),将每个MTR(底层页面一次原子访问)运行产生的日子暂存到一个地方,当MTR结束时复制到log buffer。
redo log刷盘时机:
1、log buffer空间不足时,达到50%左右。
2、事务提交时。
3、脏页刷新到磁盘前会现将对应的redo日志刷盘。
4、后台线程大约1/s每次将log buffer中的redo日志刷盘。
5、正常关闭服务器。
6、checkpoint时。
redo日志是先写到log buffer中再刷新到磁盘的redo日志文件中,由ib_logfile[x]记录,默认2个每个48MB,循环写入,为避免追尾引入lsn记录当前已经写入的redo日志量(包括写入log buffer还未刷盘的日志),buf_next_to_write记录当前log buffer有哪些日志被刷新到磁盘,flushed_to_disk_lsn表示刷新到磁盘的redo日志量。checkpoint_lsn表示数据已持久化节点,之前的redo日志都可被覆盖。
一次MTR结束后除了写入log buffer中,还需要更新buffer pool的flush链表,记录更改的页及页初始的lsn以及更新后的lsn。
redo log如何启动时恢复数据:
通过checkpoint_lsn找到文件组中的偏移量checkpoint_offset为起点,顺序扫描redo日志文件的block,直到某个block的LOG_BLOCK_HDR_DATA_LEN不为512为止。同时引入两点优化措施:1、通过hash将对同一个页面的修改放到一个槽内,一次性修复一个页面减少随机IO;2、跳过已经刷新到磁盘的页面(file header内的file_page_lsn值大于checkpoint_lsn的代表页面已经刷新到磁盘)。
为了遇到异常或者执行回滚记录的日志。
事务ID全局维护变量,需要事务时将当前值分配同时将变量+1,变量值为256的整数倍时刷新到页面为5的Max Trx IDshang。
新增操作:记录undo log时,只针对聚簇索引记录一条日志。
删除操作:先将delete_flag从0改为1,此时为中间状态记录仍在记录链上,待事务提交后加入垃圾链表头部,新增数据时先判断垃圾链表头节点空间是否够用(不会向后遍历),不够用向页面申请新的空间,当页面快满时,检查整个页剩余空间是否够用,够用则将记录顺序插入一个临时页再复制回本页面,清理掉碎片重新分配。
更新操作:1、就地更新--不更新主键且更新前后每个列占用的空间大小完全一致,则执行就地更新;2、先删除再插入--不更新主键且更新前后至少有一列占用空间不一致,则先将记录从记录链表移除到垃圾链表,再执行新增操作。3、更新主键时先将记录进行delete_mark操作,然后创建一条新的记录插入聚簇索引,事务提交后将原记录移入垃圾链表。
undo log恢复时的作用:
服务启动时先按照redo日志将数据恢复到崩溃前的状态,但有可能部分未提交的事务写的redo日志也已经刷盘,所以在启动服务时还会根据1、在表空间定位到128个回滚段的位置,在每个回滚段的1024个undo slot找到值不为fil_null的undo_slot;2、如果值为TRX_UNDO_ACTIVE代表是一个活跃的事务;3、找到对应事务的id等信息;4、通过undo日志记录的信息回滚。
事务并发执行时的一致性问题:
1、脏写:一个事务修改了另一个未提交事务修改的数据。(任何隔离级别都不会发生)
2、脏读:一个事务读到了另一个未提交事务修改的数据。
3、不可重复读:同一个事务中,两次读取的数据不一致。
4、幻读:一个事务先根据搜索条件查询出一些记录,事务未提交时另一个事务写入了符合搜索条件的记录,导致读到与之前不同的结果集。
隔离级别:
MySQL支持以上4种隔离级别,默认位 REPEATABLE READ(一定程度解决幻读问题,反例为T1查询,T2插入一条,T1对T2插入的记录执行UPDATA后trx_id变为T1的,T1再执行查询即可查询到结果)。
Oracle仅支持READ COMMITTED、SERIALZABLE,默认为READ COMMITTED。
Mysql中读已提交、可重复读实现区别:
读已提交时每次读都会创建一个新的READ VIEW,可重复度只在第一次读的时候创建一次READ VIEW。
READ VIEW:一致性视图,记录m_ids-当前系统中活跃的读写事务id列表;min_trx_id-当前活跃读写事务中的最小值;max_trx_id-分配给下一个事物的id;creator_trx_id-生成该READ VIEW的事务id。访问某条记录时,通过trx_id-被访问版本的事务id与以上几个值比较判断记录是否可见:trx_id=creator_trx_id表示访问自己可被访问;trx_id 二级索引与MVCC: 由于只有在聚簇索引中才有隐藏列trx_id和roll_pointer,故针对二级索引的并发访问控制是先判断二级索引页面的PageHeader里的PAGE_MAX_TRD_ID(对页面进行增删改操作时更新)是否小于min_trx_id,是则代表本页面均可访问,否则回表后判断可见性。 共享锁:简称S锁,在读取一条记录时,需要先获取该记录的S锁。在语句后加LOCK IN SHARE MODE。 独占锁:简称X锁,也叫排他锁,在修改一条记录时,需要先获取该记录的X锁。在语句后加FOR UPDATE。 S锁与S锁兼容,S锁与X锁不兼容,X锁与X锁也不兼容。 意向共享锁:简称IS锁,当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。 意向独占锁:简称IX锁,当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。 AUTO_INCREMENT(自增约束的实现):可指定1、2或系统自行判断何时使用哪个(主从复制场景不安全)。 1、插入时采用AUTO_INC锁,是一个表级锁,语句执行后自动释放锁,适用于插入数量不确定时。 2、轻量级锁,在生成插入语句需要的AUTO_INCREMENT修饰列的值后即释放锁,适用于插入数量确定时。 Record Lock:正经记录锁,S锁、X锁属于此类。 Gap Lock:防止插入幻影记录,锁住当前记录到上一条记录的区间,如锁记录8时为(3,8)。 Next-Key Lock:锁住当前记录且防止插入幻影记录(Record Lock+Gap Lock)。 Insert Interntion Lock:插入意向锁,插入数据时判断插入位置是否被加了Gap锁(Next-Key Lock也包含),是则等待锁释放,释放后所有插入意向锁的is_waiting都改为false,插入意向锁可兼容,互不干扰。 锁解决并发一致性问题: type:range 1、当隔离级别不大于READ COMMITTED时,加Record Lock(使用二级索引时,二级索引、聚簇索引都加),判断条件不符合时释放锁资源,符合时不释放。 2、当隔离级别不小于REPEATABLE READ时,加Next-Key Lock(使用二级索引时,二级索引加Next-Key Lock,聚簇索引加Record Lock),不论判断条件是否符合都不释放锁资源。 type:ref 不为第一条不符合条件的加锁,隔离级别不小于REPEATABLE READ时,加Gap Lock。锁: