欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MYSQL学习笔记(基础)

程序员文章站 2022-05-03 19:18:43
...

一、基础架构:一条SQL查询语句执行过程


本篇文章是个人学习笔记,不做商业用途

MySQL的逻辑架构图(图片来自极客时间mysql实战45讲)

MYSQL学习笔记(基础)

由上图所示可以分成Server层和存储引擎层

Server层

包含连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层

负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认存储引擎。

连接器

负责跟客户端建立连接、获取权限、维持和管理连接。

输入连接命令

mysql -h$ip -P$port -u$user -p

在客户端通过mysql连接命令跟服务端建立连接,完成TCP握手后,会校验用户和密码

  • 如果用户名或密码不对,会出现"Access denied for user"错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。也就是说,在修改完权限之后需要重新连接才能生效。

连接完成后,若一直没有操作,这个连接就处于空闲状态,可以用show processlist命令查看。

如果客户端一段时间没动静,连接器就会自动断开。这个时间是由参数wait_timeout控制的,默认值是8小时。

关于长连接造成的mysql资源占用大的两点解决方案:

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  2. 如果用的是MySQL 5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
查询缓存

可通过SQL_CACHE来显式指定,select sql_cache * from T where ID = 10;

不建议使用查询缓存,容易造成查询失效非常频繁,在mysql8.0版本以后就将查询缓存功能删除了。

分析器

分析器会对SQL语句做“词法分析”。输入的是由多个字符串和空格组成的一条SQL语句,MySQL需要识别出里面的字符串分别是什么,代表什么。

MySQL从输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名T”,把字符串“ID”识别成“列ID”。

接下来就是做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断输入的这个SQL语句是否满足MySQL语法。

优化器

经过了分析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。

执行器

执行前要判断有没有查询权限,没有就报错了

select * from T where ID=10;
  1. 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
小结

1.连接
连接管理模块,接收请求;连接进程和用户模块,通过,连接线程和客户端对接
2.查询
查询缓存 Query Cache
分析器:内建解析树,对其语法检查,先from,再on,再join,再where…;检查权限,生成新的解析树,语义检查(没有字段k在这里)等
优化器:将前面解析树转换成执行计划,并进行评估最优
执行器:获取锁,打开表,通过meta数据,获取数据
3.返回结果
返回给连接进程和用户模块,然后清理,等待新的请求

问题

如果表T中没有字段k,执行 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。是哪一步的问题?

答案:分析器

1.MySQL的框架有几个组件, 各是什么作用?

连接器:负责跟客户端建立连接、获取权限、维持和管理连接。
查询缓存:将缓存过的数据直接返回,提高查询效率。不过还是不建议使用查询缓存,这样容易造成查询失效非常频繁,如果是频繁更新的库表,查询缓存的命中率会非常低。在mysql8.0版本以后就将查询缓存功能删除了。
分析器:词法分析语法分析。
优化器:执行计划生成,索引选择。
执行器:操作引擎,返回结果。
存储引擎组成:存储数据,提供读写接口。

2.Server层和存储引擎层各是什么作用?

Servrer层包含连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,从MySQL 5.5.5版本开始成为了默认存储引擎。

3.you have an error in your SQL syntax 这个保存是在词法分析里还是在语法分析里报错?

语法分析报错。

4.对于表的操作权限验证在哪里进行?

执行器。

5.执行器的执行查询语句的流程是什么样的?

1).调用InnoDB引擎接口取这个表的第一行,判断是否符合条件,如果不是则跳过,如果是则将这行存在结果集中;
2).调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3).执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

二、日志系统:一条SQL更新语句执行过程

先创建语句

create table A(ID int primary key, c int);

将ID=2这一行的值加1

update A set c=c+1 where ID=2;

更新语句的执行流程跟查询语句执行流程是一样的,不过分析器和执行器的处理逻辑上有所差别,分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用ID这个索引。然后,执行器负责具体执行,找到这一行,然后更新。

与查询流程不一样的是,更新流程还涉及两个重要的日志模块:redo log(重做日志)和 binlog(归档日志)。

重要的日志模块:redo log(重做日志)

当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log 里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在系统空闲的时候,将这个操作记录更新到磁盘里面。

InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下图所示。(图片来自极客时间mysql实战45讲)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PEdvFHj8-1571794840406)(/Users/mac/Library/Application Support/typora-user-images/image-20190917091547169.png)]

write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。

write pos和checkpoint之间的是未写的部分,可以用来记录新的操作。如果write pos追上checkpoint,表示空间满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。

有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe

crash-safe这个概念可以理解为,只要redo log里面存的数据,服务器宕机了,也是能把数据刷新到磁盘。

重要的日志模块:binlog(归档日志)

MySQL整体来看,其实就有两块:一块是Server层,它主要做的是MySQL功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。redo log是InnoDB引擎特有的日志,而Server层也有自己的日志,称为binlog(归档日志)。

最开始MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是另一个公司以插件形式引入MySQL的,既然只依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套日志系统——也就是redo log来实现crash-safe能力。

这两种日志有以下三点不同。

  1. redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
  2. redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
  3. redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

执行器和InnoDB引擎在执行update语句时的内部流程。

  1. 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。

  2. 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。

  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。

  4. 执行器生成这个操作的binlog,并把binlog写入磁盘。

  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。

    浅色框表示是在InnoDB内部执行,深色框表示是在执行器中执行(图片来自极客时间mysql实战45讲)

    MYSQL学习笔记(基础)

最后3步将redo log的写入拆成了两个步骤:prepare和commit,这就是"两阶段提交"

两阶段提交

两阶段提交是为了让两份日志之间的逻辑一致,binlog会记录所有的逻辑操作,并且是采用“追加写”的形式

当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,步骤如下:

  • 首先,找到最近的一次全量备份,从这个备份恢复到临时库;
  • 然后,从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻。

这样临时库就跟误删之前的线上库一样了,然后可以把表数据从临时库取出来,按需要恢复到线上库去。

由于redo log和binlog是两个独立的逻辑,如果不用两阶段提交,要么就是先写完redo log再写binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。

  1. 先写redo log后写binlog。假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。
    但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。
    然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。
  2. 先写binlog后写redo log。如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。

可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

其核心就是, redo log 记录的,即使异常重启,都会刷新到磁盘,而 bin log 记录的, 则主要用于备份。

小结

MySQL里面最重要的两个日志:物理日志redo log和逻辑日志binlog。

Redo log是记录这个行在这个页 “做了什么改动”。

Binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有。

redo log用于保证crash-safe能力。innodb_flush_log_at_trx_commit这个参数设置成1的时候,表示每次事务的redo log都直接持久化到磁盘。这个参数建议设置成1,这样可以保证MySQL异常重启之后数据不丢失。

sync_binlog这个参数设置成1的时候,表示每次事务的binlog都持久化到磁盘。这个参数也建议设置成1,这样可以保证MySQL异常重启之后binlog不丢失。

两阶段提交:两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案,即使不做数据库内核开发,日常开发中也有可能会用到。

问题
  1. 如果提交事务的时候正好重启那么redo log和binlog会怎么处理?此时redo log处于prepare阶段,如果不接受这条log,但是binlog已经接受,还是说binlog会去检查redo log的状态,状态为prepare的不会恢复?

    Binlog如果已经接受,那么redolog是prepare, binlog已经完整了对吧,这时候崩溃恢复过程会认可这个事务,提交掉。

    1 prepare阶段 2 写binlog 3 commit
    当在2之前崩溃时
    重启恢复:后发现没有commit,回滚。备份恢复:没有binlog 。
    一致
    当在3之前崩溃
    重启恢复:虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。备份:有binlog. 一致

三、MYSQL事务

隔离性与隔离级别

四大事务:ACID
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)

当多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

SQL标准的事务隔离级:
读未提交(read uncommitted):别人改数据的事务尚未提交,我在我的事务中也能读到。
读已提交(read committed) :别人改数据的事务已经提交,我在我的事务中才能读到。
可重复读(repeatable read) :别人改数据的事务已经提交,我在我的事务中也不去读。
串行化(serializable ) :我的事务尚未提交,别人就别想改数据。
这4种隔离级别,并行性能依次降低,安全性依次提高。

隔离级别设置为读提交的配置方式:启动参数transaction-isolation的值设置成READ-COMMITTED。

事务隔离的实现

每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。

回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。

什么时候不需要了?当系统里没有比这个回滚日志更早的read-view的时候。

为什么尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间。除此之外,长事务还占用锁资源,可能会拖垮库。

事务启动方式

一、显式启动事务语句,begin或者start transaction,提交commit,回滚rollback;二、set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动

提交,直到主动执行commit或rollback或断开连接。

建议使用方法一,如果考虑多一次交互问题,可以使用commit work and chain语法。在autocommit=1的情况下用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain则提交事务并自动启动下一个事务。

总结

1、事务的特性:原子性、一致性、隔离性、持久性
2、多事务同时执行的时候,可能会出现的问题:脏读、不可重复读、幻读
3、事务隔离级别:读未提交、读提交、可重复读、串行化
4、不同事务隔离级别的区别:
读未提交:一个事务还未提交,它所做的变更就可以被别的事务看到
读提交:一个事务提交之后,它所做的变更才可以被别的事务看到
可重复读:一个事务执行过程中看到的数据是一致的。未提交的更改对其他事务是不可见的
串行化:对应一个记录会加读写锁,出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行
5、事务隔离的实现:每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。
6、回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。
7、什么时候不需要了?当系统里么有比这个回滚日志更早的read-view的时候。
8、为什么尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间。除此之外,长事务还占用锁资源,可能会拖垮库。
9、事务启动方式:一、显式启动事务语句,begin或者start transaction,提交commit,回滚rollback;二、set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动提交,直到主动执行commit或rollback或断开连接。
10、建议使用方法一,如果考虑多一次交互问题,可以使用commit work and chain语法。在autocommit=1的情况下用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain则提交事务并自动启动下一个事务。

相关标签: mysql