MySQL教案
`121.MySQL介绍
背景
:
MySQL是一个关系数据库管理系统,由瑞典MySQL AB 属于公司开发 Oracle 旗下产品。MySQL 是最流行的关系数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。 MySQL它是一个关系数据库管理系统,它将数据保存在不同的表中,而不是将所有数据放在大仓库中,从而提高速度和灵活性。 MySQL所使用的 SQL 语言是访问数据库最常用的标准化语言。MySQL 软件采用双授权政策,分为社区版和商业版。由于其体积小、速度快、整体成本低,特别是开源代码,一般选择开发中小型网站 MySQL 选择网站数据库作为网站数据库 MySQL 作为网站数据库
SQL
:
SQL (Structured Query Language) 它是一种具有数据操作和数据定义等多种功能的数据库语言。该语言具有互动特性,可为用户提供极大的便利。数据库管理系统应充分利用SQL提高计算机应用系统的工作质量和效率。SQL语言不仅可以独立应用于终端,还可以作为子语言为其他程序设计提供有效的帮助,SQL可以与其他程序语言一起优化程序功能,为用户提供更全面的信息
关系数据库:
关系数据库是指利用关系模型组织数据的数据库,以行和列的形式存储数据,以便用户理解。关系数据库的行和列称为表,一组表形成数据库。
用户通过查询检索数据库中的数据,查询是限制数据库中某些区域的执行代码。关系模型可以简单地理解为二维表模型,而关系数据库是由二维表及其关系组成的数据组织。
1.存储方式:传统的关系数据库采用表格存储方式,数据以行和列的方式存储,读取和查询非常方便。 2.存储结构:关系数据库以结构化的方式存储数据。每个数据表必须定义每个字段(即首先定义表的结构),然后根据表的结构存储数据。这样做的好处是数据的形式和内容在存储数据之前已经定义,因此整个数据表的可靠性和稳定性相对较高,但问题是一旦存储数据,如果需要修改数据表的结构就会十分困难。 3.存储规范:为了避免重复、标准化数据,充分利用存储空间,关系数据库以最小关系表的形式存储数据,使数据管理一目了然。当然,这主要是一个数据表。如果是多个表,情况就不同了。由于数据涉及多个数据表,数据表之间存在着复杂的关系。随着数据表数量的增加,数据管理将变得越来越复杂。 4.扩展模式:由于关系数据库将数据存储在数据表中,数据操作的瓶颈出现在多个数据表的操作中,数据表越多,问题就越严重。如果你想缓解这个问题,你只能提高处理能力,也就是说,选择性能更快的计算机。虽然这种方法可以在一定程度上扩展空间,但这种扩展空间必须非常有限,也就是说,关系数据库只有纵向扩展能力。 5.查询方式:关系数据库采用结构化查询语言(即)SQL)查询数据库,SQL已经得到了各种数据库制造商的支持,成为数据库行业的标准,可以支持数据库CRUD(添加、查询、更新、删除)操作,功能非常强大,SQL可采用类似索引的方法加快查询操作。 6.标准化:在数据库的设计和开发过程中,开发人员通常需要同时操作一个或多个数据实体(包括数组、列表和嵌套数据),因此在关系数据库中,数据实体一般分为多个部分,然后标准化分割部分,存储在多个关系数据表中,这是一个复杂的过程。好消息是,随着软件技术的发展,相当多的软件开发平台提供了一些简单的解决方案,例如,可以使用ORM基于数据库中对象模型的映射(即对象关系映射)SQL不同类型系统的数据之间的关系数据库和转换。 7.事务性关系数据库强调ACID规则(原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)),能满足事务性要求高或数据查询复杂的数据操作,能充分满足数据库操作性能高、操作稳定性高的要求。而且关系数据库强调数据的强一致性,对事务操作有很好的支持。而且关系数据库强调数据的强一致性,对事务操作有很好的支持。关系数据库可以控制事务的原子细粒度,一旦操作错误或需要,可以立即回滚事务。 8.读写性能:关系数据库强调数据的一致性,为降低读写性能付出了巨大的代价。虽然关系数据库存储数据和处理数据的可靠性非常好,但一旦处理大量数据,效率就会变得非常差,尤其是高并发读写时。 9.授权方式:关系数据库很常见 Oracle,SQLServer,DB2,Mysql,除了Mysql如果使用大多数关系数据库,即使是免费的,也需要支付高昂的费用Mysql性能也受到很多限制。
2.MySQL功能
1.存储关系数据.查询 2.查询方式,只是sql 3.支持事务 4.提供接口访问 5.是否只是扩展来应对 超大数据集 和 高并发 6.存储 数据表的 行信息 表信息 索引信息 如何在磁盘中设计数据结构? 7.缓存功能 8.词法分析功能 9.日志功能
3.MySQL需要实现的
1.磁盘IO 与 内存 高效交换数据 2.网络IO(tcp 请求的) 与 内存 3.sql规则制定 4.sql解析 词法分析 4,sql优化. sql语句优化 5.sql缓存 bufferPool 数据页缓存 6.数据结构设计 1).如何在磁盘中存储MySQL数据,
基本数据,索引数据 2).磁盘和内存交互需要设计哪些数据结构? 如何存储磁盘,如何存储内存,中间数据载体
4.MySQL的设计
链路的大致执行
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-Xjhn2XHz-1645341497758)(image/kdf1xly_h7.png “”)]
索引结构
B-Tree
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-1CUdQolG-1645341497761)(image/egv9m3be7e.png “”)]
B Tree
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-GILYPMjW-1645341497762)(image/7bkck2yguk.png “”)]
Hash
存储引擎及其索引文件的结构
MySAM
InnoDB
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-5c7ivPdj-1645341497762)(image/v-68-bbg9y.png “”)]
文件结构
一条mysql数据 ==>对应数据行结构数据, 数据行: 数据内容、空值信息、各字段信息、下一行数据信息等 ==>数据页面由多行组成 数据页: 存储多个数据行,存储多个数据行B 树信息,cache信息(数据行统计数据) 容量信息等. ==>数据页 属于某个区,段信息,等等. 也就是表空间 这部分是数据存储单元,即处理磁盘. 层层包装,建立适当的数据结构,将信息串联在一起.
,它是InnoDB
管理存储空间的基本单位,一个页面的大小一般是16KB
。InnoDB
设计了许多不同类型的不同目的页
,例如,存储表空间头部信息的页面,存储Insert Buffer
存储信息页面INODE
存储信息页面undo
日志信息页面
-
页是
MySQL
中磁盘与内存交互的基本单位也是MySQL
是管理存储空间的基本单位。 -
指定和修改行格式的语法如下:
CREATE TABLE 表名 (列信息) ROW_FORMAT=行格式名称 ALTER TABLE 表名 ROW_FORMAT=行格式名称
-
InnoDB
目前定义了4种行格式-
COMPACT行格式 具体组成如图所示: [外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-Gv2eJ4va-1645341497763)(image/01mifdp89c.png “”)]
-
Redundant行格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cFjSQtMm-1645341497764)(image/rqu7-cyl6x.png “”)]
-
Dynamic和Compressed行格式 这两种行格式类似于
COMPACT行格式
,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字符串的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。 另外,Compressed
行格式会采用压缩算法对页面进行压缩。
-
- 一个页一般是
16KB
,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出
。
,它是InnoDB
管理存储空间的基本单位,一个页的大小一般是16KB
。InnoDB
为了不同的目的而设计了许多种不同类型的页
,比如存放表空间头部信息的页,存放Insert Buffer
信息的页,存放INODE
信息的页,存放undo
日志信息的页
- InnoDB为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做
数据页
。 - 一个数据页可以被大致划分为7个部分,分别是
File Header
,表示页的一些通用信息,占固定的38字节。Page Header
,表示数据页专有的一些信息,占固定的56个字节。Infimum + Supremum
,两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的26
个字节。User Records
:真实存储我们插入的记录的部分,大小不固定。Free Space
:页中尚未使用的部分,大小不确定。Page Directory
:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的空间越多。File Trailer
:用于检验页是否完整的部分,占用固定的8个字节。
- 每个记录的头信息中都有一个
next_record
属性,从而使页中的所有记录串联成一个单链表
。 InnoDB
会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个槽
,存放在Page Directory
中,所以在一个页中根据主键查找记录是非常快的,分为两步:- 通过二分法确定该记录所在的槽。
- 通过记录的next_record属性遍历该槽所在的组中的各个记录。
- 每个数据页的
File Header
部分都有上一个和下一个页的编号,所以所有的数据页会组成一个双链表
。 - 为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的
LSN
值,如果首部和尾部的校验和和LSN
值校验不成功的话,就说明同步过程出现了问题。
表空间
是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ibd
的实际文件。大家可以把表空间想象成被切分为许许多多个页
的池子,当我们想为某个表插入一条记录的时候,就从池子中捞出一个对应的页来把数据写进去
事务隔离
我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。
这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了锁机制、MVCC多版本并发控制隔离机制,用一整套机制来。接下来,我们会深入讲解这些机制,让大家彻底理解数据库内部的执行原理。
1.事务设计
事务是描述一组sql组成的逻辑单元,他有4个属性.
并发的事务的时候,会引起各种问题.
==> 脏读,脏写,不可重复读,幻读.
==> 设计了4种隔离级别,来解决各种问题.
==> 不同的隔离级别,要承担的风险不一样,
读已提交rc 并发性性能好,但是会出现不可重复读和幻读的问题
可重复读rr
● 并发性能不如上面,会有间隙锁(导致出现死锁的几率比RC大的多!),等锁的问题发生.
● 条件列未命中索引会锁表! 扫描非索引条件会走举簇索引然后type=All 然后锁去全表 而在RC隔离级别下,只锁行
但是,代码能解决可重读读的问题.
序列化 都是用锁,来实现,读读 写读,读写,写读都上锁. 效率低
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
- 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
- 隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
- 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
更新丢失(Lost Update)或脏写
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–。
脏读(Dirty Reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一句话:,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
不可重读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:
幻读(Phantom Reads)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
一句话:
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JFTUcr4L-1645341497765)(https://note.youdao.com/yws/public/resource/354ae85f3519bac0581919a458278a59/xmlnote/74624CB778F948349A31BA0A40430F51/98786 “”)]
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。
**设置事务隔离级别:**
MVCC机制
实现,rc,rr的隔离级别,有两种方案
1.mvcc 解决并发读 锁解决并发写
2.读写锁,类似reentrantReadWriteLock.
归纳
1.全局按执行顺序排序的事务id
+
2.readView 视图机制
3.版本链思路
解决 两种隔离级别下的并发读的问题.
思路:
隔离级别与时间线相关.==> 事务id的实现
所有数据在哪记录 ==> 版本链
此时所有活跃事务信息统计==>视图机制.
● readview
维护活跃的 tra_ids min_id max_id
min_id 活跃里面最小的
max_id 代表应该分配给下一个事务的id , 并不是ids 里面最大的. 因为此时ids里面最大的(比如200),如果他一直不提交,后进来的(300) 300 已经提交了. 所以301 就是max_id. 版本链 第一条数据就是300.
从版本链里面拿数据
如果大于max_id,不管rc,rr,都代表这个数据集是不可见的.代表超越当前版本的数据 (如果是在rc,里本身拿的就是顺时值,不可能出现啊,如果是rr,那可以理解,因为可重复读这个事务执行的时候,可能已经变更好几轮了.)
如果小于min_id ,代表不是活跃的 并且也小于 max_id 代表都是已经提交过的. 是可见的.
不管是rc,rr 都是代表提交过的
如果是在两者之间.
那么他要么已经提交,要么还未提交.
判断是否在ids,在的话,不可用, 不在的话,就是已经提交过的了.
对于rc来说,每次都取新值ReadView,所以,他拿到的ids 就是实时的.
对于rr来说,只在select的时候拿依次readview ,那么这个ids和maxid,还有minid 就限制了他能拿的数据
就一直是,ReadView里,与版本链对应的,可见的数据.在 小于max_id的这个范围内.
对于rr以后的阶段,版本链会更新新的数据,(已提交,未提交) ids变了(更新更多的数据). maxid变大 .
还想用原来的那批数据. 要么小于min,要么不在ids里面,且大于min,小于max.
新commit的数据一定在 旧的ids里面,以及大于max里面.
还得需要排除掉版本链里
结论
==>需要排除掉版本链里面 新commit的,以及正update的. 新commit的要么在旧的ids里面,要么在大于maxid 里面.
所以,只用旧的readview 就能排除掉这些元素. 所以 rr 只取一次就能拿到需要的数据
rc
每次查询获取ReadView 从版本链,依次向后拿数据,拿到第一条,不在活跃事务ids里的数据就是答案
rr
只拿一次readView,就拿当前视图里,可见的数据,版本链里第一条不在ids里面的
tips :
1.一条行数据,只能给一个事务正在修改. 因为,update 要上行锁.
事务id
mysql,维护每一个事务在准备执行变更(update,delete,insert)的时候,会获得一个事务id.
全局公用一份
SHOW ENGINE INNODB STATUS 通过这个可以看到,下一份需要生成的事务id
生成规则:(每次 变更操作的事务,就给一个)
如果应对关机呢: 就定期发送数据到磁盘. 下次重启,空数据补齐.
对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增、删、改操作时才会为这个事务分配一个事务id
,否则的话也是不分配事务id
的。
事务id是怎么生成的
这个事务id
本质上就是一个数字,它的分配策略和我们前边提到的对隐藏列row_id
(当用户没有为表创建主键和UNIQUE
键时InnoDB
自动创建的列)的分配策略大抵相同,具体策略如下:
- 服务器会在内存中维护一个全局变量,每当需要为某个事务分配一个
事务id
时,就会把该变量的值当作事务id
分配给该事务,并且把该变量自增1。 - 每当这个变量的值为
256
的倍数时,就会将该变量的值刷新到系统表空间的页号为5
的页面中一个称之为Max Trx ID
的属性处,这个属性占用8
个字节的存储空间。 - 当系统下一次重新启动时,会将上边提到的
Max Trx ID
属性加载到内存中,将该值加上256之后赋值给我们前边提到的全局变量(因为在上次关机时该全局变量的值可能大于Max Trx ID
属性值)。
这样就可以保证整个系统中分配的事务id
值是一个递增的数字。先被分配id
的事务得到的是较小的事务id
,后被分配id
的事务得到的是较大的事务id
。
图解解释
参考掘金小册表达的: https://juejin.cn/book/6844733769996304392/section/6844733770071801870
锁机制
概述
首先不经常用.
只是mysql提供了某些操作.如共享锁和独占锁.
设计上,还有间隙锁,表锁,隐士的锁. 等等.
在隔离级别设计与实现上. 会使用行锁. (解决并发写的问题.)
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
从性能上分为乐观锁(用版本对比来实现)和悲观锁
从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)
读锁(共享锁,S锁(hared)):
针对同一份数据,多个读操作可以同时进行而不会互相影响,有读锁的时候,不允许加写锁.
select ***** LOCK IN SHARE MODE
写锁(排它锁,X锁(eclusive)):
当前写操作没有完成前,它会阻断其他写锁和读锁
update的时候,或者 for update
从对数据操作的粒度分,分为表锁和行锁
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。
每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自动给涉及的表加写锁。
InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。
简而言之,就是
● 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
● 合理设计索引,尽量缩小锁的范围
● 尽可能减少检索条件范围,避免间隙锁
● 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
● 尽可能低级别事务隔离
问题
共享锁独占锁这两个锁,是为了解决什么问题,存在的?
有的时候的业务需求,可以使用
共享锁,的目的是想让你读的时候,不允许被update. 某些银行业务系统需要再你读的时候,不允许被update,转账的时候
LOCK IN SHARE MODE
独占锁.是写写,写读,读写,互斥. for update
mvcc 机制解决了 并发产生的(脏读,不可重复读) 的问题. 那么能解决写并发的问题吗??
mvcc只是为读操作设计的机制,是一种无锁的操作.
而写并发的问题(写入数据有序,不错乱,比如一起操作 10 个索引数据,如果不上锁,就是脏写)
写并发是由锁控制,如果写并发不控制,那么mvcc 就是脏写了.至于能不保证结果.
我感觉能,但是,实际咋地不知道. 反正不能是并发操作一行, 会有问题,就像java里一样
先读再写,万一写的时候读到了别人修改后的数据,在写,那不就gg了. 就跟count++ 一样
所以,并发写上锁
至于C++层面如何实现锁,使用什么操作系统指令,需要深入的时候再深入.
有什么办法解决 并发操作产生的问题吗
1.读mvcc,写用锁
2.读用读锁(共享读,不让写),写用写锁. 就跟java里的reentrantReadWriteLock 一样
mvcc 机制 与 共享锁,独占锁之间的联系.
mvcc 机制是用来解决两种隔离级别下,并发读的问题.
共享锁和独占锁,用来实现某些特殊需求的.
多粒度的锁. IS IX 这种是解决什么问题
比如,想加表锁,但是你得确保,更细粒度的锁,都释放掉才行.(遍历所有资源,看有没有锁)
所以为了确保,能使用更粗粒度的锁,
那么在加细粒度锁的时候,得再加一个意向锁,标记此时锁的状态.
这样,粗粒度的锁,看到的时候,就知道,啥子情况了.
==> 如果在java 中,这代码得咋写. 粗粒度到什么时候,意向锁,标记到哪个位置…
类里面,两个锁标记呗? 还得维护,我脑仁疼.
感觉可以,放在一个更粗粒度的地方,维护上,设计成,类似重入锁 的样子.
.
对于MyISAM
、MEMORY
、MERGE
这些存储引擎来说,它们只支持表级锁
日志设计
概述
binlog日志: 以二进制形式,记录所有变更操作(update,insert,delete 等)
undo 日志 : 和==版本链==数据相关所有版本链上的数据,记录的回滚操作内容
redo 日志: 和==数据页==相关,记录所有数据页变更的内容,索引树结构怎么变的,怎么移动的,数据怎么变的等等
redo日志
总述
记录,数据页操作的一个IO文件.
● 服务于持久化机制,提供一个保障(会在commit前完成写入日志文件)
● 顺序写 优于随机写
● 写一个优于写一堆
● 减少了磁盘操作
● 如果直接操作磁盘文件,还得加锁,页级别的锁.并发操作页,会不会有影响,所以锁的粒度也升级了
所以磁盘操作,还是异步合适.
我们知道InnoDB
存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。我们前边唠叨Buffer Pool
的时候说过,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool
之后才可以访问。但是在唠叨事务的时候又强调过一个称之为持久性
的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据库中所做的更改也不能丢失。但是如果我们只在内存的Buffer Pool
中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了,这是我们所不能忍受的(想想ATM机已经提示狗哥转账成功,但之后由于服务器出现故障,重启之后猫爷发现自己没收到钱,猫爷就被砍死了)。那么如何保证这个持久性
呢?一个很简单的做法就是在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:
- 刷新一个完整的数据页太浪费了 有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在
InnoDB
中是以页为单位来进行磁盘IO的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据到磁盘上显然是太浪费了。 - 随机IO刷起来比较慢 一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,倒霉催的是该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的
Buffer Pool
中的页面刷新到磁盘时,需要进行很多的随机IO,随机IO比顺序IO要慢,尤其对于传统的机械硬盘来说。
咋办呢?再次回到我们的初心:我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好,比方说某个事务将系统表空间中的第100号页面中偏移量为1000处的那个字节的值1
改成2
我们只需要记录一下:
将第0号表空间的100号页面的偏移量为1000处的值更新为
2
。
这样我们在事务提交时,把上述内容刷新到磁盘中,即使之后系统崩溃了,重启之后只要按照上述内容所记录的步骤重新更新一下数据页,那么该事务对数据库中所做的修改又可以被恢复出来,也就意味着满足持久性
的要求。因为在系统崩溃重启时需要按照上述内容所记录的步骤重新更新数据页,所以上述内容也被称之为重做日志
,英文名为redo log
,我们也可以土洋结合,称之为redo日志
。与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的redo
日志刷新到磁盘的好处如下:
redo
日志占用的空间非常小 存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的,关于redo
日志的格式我们稍后会详细唠叨,现在只要知道一条redo
日志占用的空间不是很大就好了。redo
日志是顺序写入磁盘的 在执行事务的过程中,每执行一条语句,就可能产生若干条redo
日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO
unDo日志
目的:
● 为了回滚数据用的
● 记录在某个数据页中,表空间归属不知道.
事务回滚的需求
我们说过事务
需要保证原子性
,也就是事务中的操作要么全部完成,要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况,比如:
- 情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。
- 情况二:程序员可以在事务执行过程中手动输入
ROLLBACK
语句结束当前的事务的执行。
这两种情况都会导致事务执行到一半就结束,但是事务执行过程中可能已经修改了很多东西,为了保证事务的原子性,我们需要把东西改回原先的样子,这个过程就称之为回滚
(英文名:rollback
),这样就可以造成一个假象:这个事务看起来什么都没做,所以符合原子性
要求。
小时候我非常痴迷于象棋,总是想找厉害的大人下棋,赢棋是不可能赢棋的,这辈子都不可能赢棋的,又不想认输,只能偷偷的悔棋才能勉强玩的下去。悔棋
就是一种非常典型的回滚
操作,比如棋子往前走两步,悔棋
对应的操作就是向后走两步;比如棋子往左走一步,悔棋
对应的操作就是向右走一步。数据库中的回滚跟悔棋
差不多,你插入了一条记录,回滚
操作对应的就是把这条记录删除掉;你更新了一条记录,回滚
操作对应的就是把该记录更新为旧值;你删除了一条记录,回滚
操作对应的自然就是把该记录再插进去。说的貌似很简单的样子[手动偷笑😏]。
从上边的描述中我们已经能隐约感觉到,每当我们要对一条记录做改动时(这里的改动
可以指INSERT
、DELETE
、UPDATE
),都需要留一手 —— 把回滚时所需的东西都给记下来。比方说:
- 你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。
- 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。
- 你修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。
设计数据库的大叔把这些为了回滚而记录的这些东东称之为撤销日志,英文名为undo log
,我们也可以土洋结合,称之为undo日志
。这里需要注意的一点是,由于查询操作(SELECT
)并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo日志
。在真实的InnoDB
中,undo日志
其实并不像我们上边所说的那么简单,不同类型的操作产生的undo日志
的格式也是不同的,不过先暂时把这些容易让人脑子糊的具体细节放一放,我们先回过头来看看事务id
是个神马玩意儿。
binlog 日志
二进制文件,记录所有变更操作. 恢复的时候,有用.
主从同步的时候,会使用
缓存机制 BufferPool
概述
1.undo日志 redo日志 binlog 日志的设计
保证数据能有效,能回滚,能恢复,能应对异常情况.
并且只是是异步操作磁盘,效率很高.
2.bufferpool 里面 的冷热lru 链表,手动实现基本功能.
总结
-
磁盘太慢,用内存作为缓存很有必要。
-
Buffer Pool
本质上是InnoDB
向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size
来调整它的大小。 -
Buffer Pool
向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后,Buffer Pool
剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为碎片
。 -
InnoDB
使用了许多链表
来管理Buffer Pool
。 -
free链表
中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到Buffer Pool
时,会从free链表
中寻找空闲的缓存页。 -
为了快速定位某个页是否被加载到
Buffer Pool
,使用表空间号 + 页号
作为key
,缓存页作为value
,建立哈希表。 -
在
Buffer Pool
中被修改的页称为脏页
,脏页并不是立即刷新,而是被加入到flush链表
中,待之后的某个时刻同步到磁盘上。 -
LRU链表
分为young
和old
两个区域,可以通过innodb_old_blocks_pct
来调节old
区域所占的比例。首次从磁盘上加载到Buffer Pool
的页会被放到old
区域的头部,在innodb_old_blocks_time
间隔时间内访问该页不会把它移动到young
区域头部。在Buffer Pool
没有可用的空闲缓存页时,会首先淘汰掉old
区域的一些页。 -
我们可以通过指定
innodb_buffer_pool_instances
来控制Buffer Pool
实例的个数,每个Buffer Pool
实例中都有各自独立的链表,互不干扰。 -
自
MySQL 5.7.5
版本之后,可以在服务器运行过程中调整Buffer Pool
大小。每个Buffer Pool
实例由若干个chunk
组成,每个chunk
的大小可以在服务器启动时通过启动参数调整。 -
可以用下边的命令查看
Buffer Pool
的状态信息:链路图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30EtT2Ei-1645341497766)(image/a4lzc_187j.png “”)]
5.MySQL的应用
2.SQL如何使用索引
前言
现有工作中:,学习好mysql解决的目的是什么?
● 查询时间复杂度,空间复杂度
● 机器运行平稳,cpu,内存,磁盘IO,正常.
途径:
sql层面
1.建立索引
● 根据业务场景,建立合适的索引
1).索引个数
2).索引顺序
3).索引数量 varchar(100)
● 必要的时候,人工插入数据,建立索引.
2.使用索引
普通查询
● 使用索引
● 索引覆盖
● 不回表
复杂查询
order by group by
● 使用索引,同联合索引类似
● 尽量不使用filesort,如果使用filesort,尽量索引覆盖+where a ,b 的条件,能过滤掉很多数据
常见sql优化1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o3LWB0cR-1645341497767)(image/a6jkwligr3.png “”)]
使用了,联合索引的全部索引,观察key_len 就知道用了几个索引内容
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zVxacqiH-1645341497768)(image/n4tjb__e6c.png “”)]
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TsDAsKXL-1645341497768)(image/zfvgf2hz8m.png “”)]
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manage r';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB8cYgBA-1645341497769)(image/v3oph1eecn.png “”)]
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
EXPLAIN SELECT * FROM employees WHERE name = 'Bill' and age = 31;
EXPLAIN SELECT * FROM employees WHERE age = 30 AND position = 'dev';
EXPLAIN SELECT * FROM employees WHERE position = 'manager';
#都断了,所以最后都使用了第一部分的索引.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AtekzbUW-1645341497769)(image/406o63yyu-.png “”)]
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei';
EXPLAIN SELECT * FROM employees WHERE left(name,3) = 'LiLei';
#因为没有创建 left(name,3) 的值对应的索引树. 所以 用left(name,3) 只有值,没有树.所以全表.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28m0Ipgr-1645341497770)(image/fph9v049ly.png “”)]
给hire_time增加一个普通索引:
ALTER TABLE `employees` ADD INDEX `idx_hire_time` (`hire_time`) USING BTREE ;
EXPLAIN select * from employees where date(hire_time) ='2018‐09‐30';
#
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAWTVlo7-1645341497771)(image/3sxlfd8jv3.png “”)]
转化为日期范围查询,有可能会走索引:
EXPLAIN select * from employees where hire_time >='2018‐09‐30 00:00:00' and hire_time < ='2018‐09‐30 23:59:59';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ic6w3h5c-1645341497771)(image/cec5be4fds.png “”)]
还原最初索引状态
ALTER TABLE employees
DROP INDEX idx_hire_time
;
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 22 AND position ='manage r';
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age > 22 AND position ='manage r';
#观察key_len 76+2 只使用了 两个键 3*24+2 + 4
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbky370A-1645341497772)(image/iuzep8ontj.png “”)]
EXPLAIN SELECT name,age FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manager';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PUZExwc1-1645341497773)(image/b7jq9if2b_.png “”)]
EXPLAIN SELECT * FROM employees WHERE name= 'LiLei' AND age = 23 AND position ='manage r';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SJvpzHMk-1645341497773)(image/iv3mu7a250.png “”)]
6.mysql在使用不等于(!=或者<>), ,
、>= 这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引
EXPLAIN SELECT * FROM employees WHERE name != 'LiLei';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcCKIecG-1645341497774)(image/5lr3t3e1-z.png “”)]
**7.is null,is not null **
一般情况下也无法使用索引
EXPLAIN SELECT * FROM employees WHERE name is null
#看建立表的时候,是否允许这个字段为null
#如果不为null 那么用null 去查询就是全表
#如果允许为null ,那么索引值里面就会包含null 的数据. 也就是会使用索引
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VpRMF3MM-1645341497774)(image/8osxu0tqsh.png “”)]
EXPLAIN SELECT * FROM employees WHERE name like '%Lei'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdx0jZrq-1645341497775)(image/8s486hkvw8.png “”)]
EXPLAIN SELECT * FROM employees WHERE name like 'Lei%'
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IIy6awyD-1645341497775)(image/i7pqy18u9x.png “”)]
:解决like’%字符串%'索引不被使用的方法?
a)使用覆盖索引,查询字段必须是建立覆盖索引字段
1 EXPLAIN SELECT name,age,position FROM employees WHERE name like ‘%Lei%’;
b)如果不能使用覆盖索引则可能需要借助搜索引擎
EXPLAIN SELECT * FROM employees WHERE name = '1000';
EXPLAIN SELECT * FROM employees WHERE name = 1000;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LS34dIQC-1645341497775)(image/8p5l978mx6.png “”)]
EXPLAIN SELECT * FROM employees WHERE name = 'LiLei' or name = 'HanMeimei';
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g2dk3yJh-1645341497776)(image/-4xk1v_vwh.png “”)]
给年龄添加单值索引
ALTER TABLE `employees` ADD INDEX `idx_age` (`age`) USING BTREE ;
explain select * from employees where age >=1 and age <=2000;
#评估索引查询所走行数,和全表扫描,哪个更优
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ly4zFIYT-1645341497776)(image/117hr23_zi.png “”)]
没走索引原因:mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。比如这个例子,可能是
由于单次数据量查询过大导致优化器最终选择不走索引
优化方法:可以将大的范围拆分成多个小范围
explain select * from employees where age >=1 and age <=1000;
explain select * from employees where age >=1001 and age <=2000;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bHFGz22P-1645341497777)(image/_iwv5lzjl2.png “”)]
还原索引状态
ALTER TABLE `employees` DROP INDEX `idx_age`;
12 总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZOTKtnVj-1645341497777)(image/gqve_vx54d.png “”)]
常见sql优化2
1、联合索引第一个字段用范围不会走索引
EXPLAIN SELECT * FROM employees WHERE name > ‘LiLei’ AND age = 22 AND position =‘manager’;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bBplQ86p-1645341497777)(https://note.youdao.com/yws/public/resource/d2e8a0ae8c9dc2a45c799b771a5899f6/xmlnote/8B6932DA0FCA46D0A473192F7832275B/98405 “”)]
结论:联合索引第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如就全表扫描
EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > ‘LiLei’ AND age = 22 AND position =‘manager’;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iEUW72nl-1645341497778)(https://note.youdao.com/yws/public/resource/d2e8a0ae8c9dc2a45c799b771a5899f6/xmlnote/E9D3568E1D6C4D1EBC587DF5EF7E8612/98401 “”)]
结论:虽然使用了强制走索引让联合索引第一个字段范围查找也走索引,扫描的行rows看上去也少了点,但是最终查找效率不一定比全表扫描高,因为回表效率不高
做了一个小实验:
– 关闭查询缓存 set global query_cache_size=0; set global query_cache_type=0; – 执行时间0.333s SELECT * FROM employees WHERE name > ‘LiLei’; – 执行时间0.444s SELECT * FROM employees force index(idx_name_age_position) WHERE name > ‘LiLei’;
EXPLAIN SELECT name,age,position FROM employees WHERE name > ‘LiLei’ AND age = 22 AND position =‘manager’;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qrblMFvz-1645341497778)(https://note.youdao.com/yws/public/resource/d2e8a0ae8c9dc2a45c799b771a5899f6/xmlnote/F19176D7224B46F18D4513EC68028072/98402 “”)]