MySQL5.7学习的笔记,如果有什么问题欢迎留言谈论,另外因为内容过多且有些还没深入了解所以可能记得不全后续会边学边补上
为什么要先了解架构?因为后面的事务、索引都是和架构相关的,而且在实操中可以对应架构体系中的每一层进行优化。
在网上找到了一个图,如下大致为MySQL的架构体系:
下面是另外一个图(这个相对上面的图简略些):
整个流程还是很好理解的:
存储引擎
这里MySQL的存储引擎是可插拔的,也就是说你可以在MySQL服务中插入别的存储引擎。
这里主要讲InnoDB、MyISAM、MEMORY,用的比较多的还是InnoDB、MyISAM。
为什么要引入事务?事务是构成单一逻辑工作单元的操作集合,保证了数据的一致性。
数据库事务4种隔离级别及7种传播行为
事务分类:扁平事务、带保存点的扁平事务、链式事务、嵌套事务。
事务的4大特性(ACID):
保证事务特性和下面的知识点有关:
① Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版本并发控制(MVCC),也即一致非锁定读。
② 在操作任何数据之前,首先将数据备份到一个地方(在这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
③ 注意undo log是逻辑日志,可以理解为:
- 当delete一条记录是,undo log中会记录一条对应的insert记录
- 当insert一条记录是,undo log中会记录一条对应的delete记录
- 当update一条记录时,它记录一条对应相反的update记录
MySQL中的逻辑日志:拿InnoDB来举例子,我们都知道InnoDB引擎的数据文件有且仅有一个,后缀为ibd文件,所以索引数据和实际的数据等等都存在同一个文件,可想而知文件会很大,特别是数据表特别多的情况下,所以我们在读取文件记录的时候不会读取整个文件并且硬件条件也不允许【文件数据是存在硬盘上的,读取后会存在内存中,假如整个文件数据为1T,你不可能有1T的内存】,而是读取某一部分。内存和硬盘进行数据交互是以‘页’为单位的,页为逻辑上的概念,一般大小为4kb或8kb等4kb整数倍【InnoDB存储引擎每一次读取为16kb】,这个值和硬盘的扇区【512B】有关系。实际生产环境中,某个时刻会有多条记录或事务在进行并发执行,这就出现每个并发操作会对同一个页中的不同数据进行操作【例如事务A对这个页中id为1的数据进行修改,事务B对这个页中id为2的数据进行修改】,所以我们不能一整个页为整体去记录undo log日志,因为这样的话只能记录到一条数据操作日志,其他的就没法被记录到了。所以我们只能以当行操作去记录undo log日志【如上面的事务A要记录一条undo log日志记录,事务B则记录另外一条undo log日志记录】。所以逻辑日志就是指实际操作的并不是记录实际存储的一个物理页,而是记录每个页中每一行数据的更新信息。
①从上面的MySQL架构中我们知道,当我们对MySQL中的数据进行修改时,并不是立马写到磁盘中,而是先写到缓存中,再写到磁盘中(fsync)。这时假设写到缓存中但是还没来得及写到磁盘中,系统就发生了断电,这时候重启系统数据也会丢失,很明显数据也不一致了。
这时候有一个概念叫WAL: Write-Ahead Logging 预写日志系统,数据库中一种高效的日志算法,对于非内存数据库而言,磁盘I/O操作是数据库效率的一大瓶颈。在相同的数据量下,采用WAL日志的数据库系统在事务提交时,磁盘写操作只有传统的回滚日志的一半左右,大大提高了数据库磁盘I/O操作的效率,从而提高了数据库的性能。
关于wal预写日志系统这里举个例子:假设你开了一个饭店,然后你有一个记账本,这个记账本十分厚有100万个人的记录,在你中午饭点时候饭店很多人你很忙,这时候有个人忘记带钱了要赊账,而你也不可能在这么忙的时候去100万个人的记账本中找出那个人,这时候你就可以弄一个小黑板,然后把那个人记下来,等有空余时间的时候再把小黑板中的记录记到记账本中。这里的小黑板就是相当于数据库事务中Redo log前滚日志。当然如果小黑板上面的记录记满了的话也要及时的把数据整理到记账本中。
②和Undo Log相反,Redo Log记录的是新数据的备份。==在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。==当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态(innodb_flush_log_at_trx_commit)。Redo Log是物理日志,它实际操作的是记录实际存储的一个物理页。
数据存储过程:
如下图所示,向数据库写数据的时候会先写到用户空间(当前进程内存中),在写到内核空间(操作系统内存),最后才写到磁盘中
Redo Log(前滚日志/重做日志)中innodb_flush_log_at_trx_commit参数说明:
undo和redo对比总结:以当前时间点来区分,undo log是保持之前数据的状态,redo log是保持之后数据的状态。
①事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。
②然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低。
③SQL标准为事务定义了不同的隔离级别,从低到高依次是:
读未提交(READ UNCOMMITTED):对事务处理的读取没有任何限制,不推荐
读已提交(READ COMMITTED)
可重复读(REPEATEBLE READ:MySQL默认的隔离级别
串行化(SERIALIZABLE)
这篇文章讲的很详细,这里就不再扩展了:脏读、幻读和不可重复读 + 事务隔离级别
另外这一篇是代码演示:MYSQL演示关系型数据库的隔离级别
MySQL锁机制
在MySQL中,锁可以分为两类:
共享锁:共享锁定是将对象数据别为只读形式,不能进行更新,所以也成为读取锁定(select 。。。。lock in share mode);
排它锁:排他锁定是当前执行INSERT/UPDATE/DALETE的时候,其它事务不能读取该数据,因此也成为写入锁定(例:select 。。。。for update)。
(如果不加上面的锁,则MySQL默认情况下为一致性非锁定读;加了上面的锁则为一致性锁定读。
另外InnoDB存储引擎和MyISAM存储引擎的锁是有区别的,InnoDB加的是行锁也可以加表锁,MyISAM加的是表锁,对于InnoDB存储引擎来说如果不通过索引条件查询的时候,则默认为表锁。)
排他锁和共享锁兼容性问题:两个都用共享锁的sql语句不会冲突,都能运行;其中有一个sql语句先使用排他锁的话,就会导致另外一条sql语句被锁死
兼容性 | 排他锁 | 共享锁 |
---|---|---|
排他锁 | 不兼容 | 不兼容 |
共享锁 | 不兼容 | 兼容 |
基于锁的并发控制流程
①事务根据自己对数据项进行的操作类型申请相应的锁(读申请共享锁,写申请排他锁) 。
②申请锁的请求被发送给锁管理器。锁管理器根据当前数据项是否已经有锁以及申请的和持有的锁是否冲突决定是否为该请求授予锁。
③若锁被授予,则申请锁的事务可以继续执行;若被拒绝,则申请锁的事务将进行等待,直到锁被其他事务释放。
锁的粒度:锁定对象的大小是锁的粒度,分别有记录、表、数据库三个粒度
另外数据库也会有死锁的情况:如下图所视(这里先设定了sql语句不自动提交,即写完sql语句后还需要commit才会隐式的提交事务),因为该表有主键,所以sql语句执行是行锁,即对每一个行记录进行加锁(或者说每个索引;主键是特殊的索引)
①这时候左边第一条sql语句先对id为1的记录进行查询,但还没提交,右边第一条sql语句对id为2的记录进行查询也是没提交。
②这时候左边第二条sql语句对id为2的记录进行查询,这时候会先卡住,然后再去右边输入第二条sql语句对id为1的记录进行查询(对应第一个图),这是因为id为2的记录已经被右边sql语句先排他锁了。
③这时候到右边输入第二条sql语句对id为1的记录进行查询,这时候左边的第二条sql语句就会显示出结果,右边第二条sql语句则报错(对应第二个图),这是因为发生了死锁了然后InnoDB引擎对死锁有检测机制:会优先回滚undo log日志量比较少的,所以右边的undo log日志量比较少所以就回滚并爆出一个错误提示,而左边因为右边回滚了导致所释放所以id为2的语句获得行锁进而得到结果。
MySQL对死锁检测有两种方式:
第一种为超时等待,如果发生死锁,在一定时间内不能两方都不释放锁的话,会自动把undo log日志量少的先把锁释放掉。
第二种为等待图,图有结点和边组成,每个结点就代表一个事务,当这个图有回环的情况时候就代表发生了死锁,就会回滚。图有两种遍历规则:广度优先遍历和深度优先遍历,等待图就用深度优先遍历进行检测。
另外有一种锁叫间隙锁,这种锁比较少被提及到,间隙锁主要是来解决幻读这种情况的。举个例子有三行数据,分别为2、4和7。这三行数据用的就是行锁,而三行数据之间范围的数据就为间隙锁,如(-∞,2)、(2,4)、(4,7)和(7,+∞)用的就是间隙锁,用了间隙锁后就不能往中间这些范围插入数据,防止幻读现象出现。此外如果用的是唯一索引,则间隙锁蜕化为行锁。(有一种叫next key的锁,它表示行锁+间隙锁)
扩展:除了锁可以实现并发控制之外,还有其他策略:
基于时间戳的并发控制
基于有效性检查的并发控制
基于快照隔离的并发控制
在事务的四个特点中,一致性是事务的根本追求,而在某些情况下会对事务的一致性造成破坏:
- 事务的并发执行
- 事务故障或系统故障
数据库系统通过并发控制技术和日志恢复技术来避免这种情况的发生
- 并发控制技术保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。
- 日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。
索引大致内容如下:
索引是什么?
索引帮助MySQL高效获取数据的数据结构。
索引存储在文件系统中。
索引的文件存储形式与存储引擎有关。(InnoDB存储引擎文件为idb文件,MyISAM存储引擎文件有两个,一个叫myd放数据文件,一个叫myi放索引文件)
索引文件的结构:hash(Memory存储引擎)、二叉树、B树、B+树(大部分数据库)。另外InnoDB存储引擎可以转化为自适应的hash表(即MySQL内部有判断机制会将B+树转换会hash表,但是该过程用户无法干预)。
索引的结构主要为B+树(官网上写了B树,但其实是B+树,另外还有hash索引和R-Tree,但是主要还是B+树)
那MySQL索引为什么选择用B+树,而不用别的数据结构,例如红黑树,二叉树或B树呢?
为什么MySQL数据库索引选择使用B+树? 、 为什么Mysql用B+树做索引而不用B树 这两篇篇文章说的很详细,可以看一下。
这里大致说一下:
了解B+树的增删之前先了解一下聚创索引和非聚簇索引:MySQL聚簇索引和非聚簇索引的理解
聚簇索引就有点像字典中按拼音排序的目录,目录的先后排序位置是和正文中先后的排序是一样的(例如:目录中拼音a在排第一,正文中拼音为a的文字也拍在最前面)。
而非聚簇索引就是除了拼音排序外的其他排序,例如偏旁部首的排序。他们正文的实际顺序和目录中的顺序不一致
然后是B树和B+树的增删(图有点多就不画了,这一篇已经讲的很详细了,而且不难理解): B树和B+树的插入、删除图文详解
然后还要注意一点InnoDB和MyISAM虽然索引都是B+树结构,但是还是有区别的,如下图所视:InnoDB的叶子节点data是直接存数据(上文说过InnoDB存储引擎文件结构只有一个文件);而MyISAM的叶子节点Data是存储地址值(MyISAM存储引擎文件结构有两个文件,一个叫MYD为数据文件,一个叫MYI为索引文件)
注意:
索引有以下的分类:主键索引、辅助索引、唯一索引、全文索引、组合索引
主键索引:数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。
辅助索引:也叫普通索引或二级索引,最基本的索引类型,没有唯一性之类的限制。例如一个表中有ID号和NAME两个属性,ID号为主键,NAME为普通列,给NAME添加索引就是辅助索引。但是要注意的是,辅助索引的叶子节点数据块放的不是实际的行记录,而是主键,然后通过主键到主键索引中找实际的行记录。(叫做回表,上面的B+树增删的第一个链接:MySQL聚簇索引和非聚簇索引的理解,里面最后一个图和例子就是回表,不懂可以看一下)
这里用两条语句,用上面说的例子:
select * from table where name =a(要回表)
select id from table where name=a(不需要回表,速度更快)
上面的第二条SQL语句为覆盖索引:在查询辅助索引的时候,如果叶子节点中保存的刚好是要查询的字段数据,那么此时就叫做索引覆盖,换句话说查询列要被所建的索引覆盖。
组合索引:多个属性组成一个索引,即为组合索引。
组合索引相关的知识点: 最左匹配原则。
索引下推:
select * from table where name=?and age=?其中name和age为组合索引。
在MySQL5.6之前的解决方案为先根据name把所有的数据查询回来,然后再server层进行age字段的数据筛选。
在MySQL5.6及以上版本则从存储引擎拉取数据的时候,会根据name,age两个字段做筛选,将符合条件的拉去回,这就是索引下推。详细一点可以看上面的链接。
扩展:谓词下推
直接一点看个例子:select t1.name,t2.name from t1 join t2 on t1.id=t2.id;这里有两个表进行连接查询,每个表有100个列。
这时候有个问题思考一下,有两种执行方法,思考一下哪一种执行的更快?
1、先把两张表按照id字段做关联,然后取出name字段。
2、先把所有需要用到的字段查询出来,然后在做关联。
答案是第2个,这里100个列为重点,也就是说除了name和id外还有98个属性,那么如果用第一种方法做关联时就会涉及到很多多余的数据,而第二种方法取出需要用的字段再关联相对会好很多。
唯一索引:不允许具有索引值相同的行,从而禁止重复的索引或键值。系统在创建该索引时检查是否有重复的键值,并在每次使用 INSERT 或 UPDATE 语句添加数据时进行检查。
唯一索引和主键索引的区别:
(1)对于主健/unique constraint , oracle/sql server/mysql等都会自动建立唯一索引;
(2)主键不一定只包含一个字段,所以如果你在主键的其中一个字段建唯一索引还是必要的;
(3)主健可作外健,唯一索引不可;
(4)主健不可为空,唯一索引可以;
(5)主健也可是多个字段的组合;
(6)主键与唯一索引不同的是:
主键索引有not null属性;
主键索引每个表只能有一个。
全文索引:这个用的比较少,大概了解一下。
MySQL explain详解
explain select XXX from XXX
这里有个关键字叫‘explain’,它是用来SQL语句的执行计划。
这里讲几个关键的列,具体可以看上面的链接:
使用show profile查询剖析工具,可以指定具体的type,具体如下:
从上面的图可以看出我们查询的时候显示的时间为0.00秒,并不是真的0秒,而是时间过小无法显示
这时候我们就要先把profiling开启,然后再进行查询一条语句。
并用show profile显示出具体过程所需要的时间,左边为执行这条语句的过程,右边为具体多长时间。
另外可以用上面的语句查看更多信息。
*注意:官网显示show profile 在后续版本可能将会被弃用,提倡用performance schema
使用performance schema来更加容易监控MySQL
performance schema是单独的一个库,这个库的存储引擎就是performance schema,它里面展示MySQL当前性能监控的东西(里面有87张表,这方面一般是由专业的DBA数据库管理员来做)。
使用show processlist查看连接的线程个数,来观察是否有大量线程处于不正常的状态或者其他不正常的特征
扩展:MySQL集群相关知识点,主从复制、读写分离、分库分表(后续学习)