MYSQL系列基本概念和SQL执行过程

2023年 7月 19日 25.2k 0

基本概念

ACID 原则

ACID,即 Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)四种特性的缩写。

ACID 也是一种比较出名的描述一致性的原则,通常出现在分布式数据库等基于事务过程的系统中。

具体来说,ACID 原则描述了分布式数据库需要满足的一致性需求,同时允许付出可用性的代价。

  • Atomicity:每次事务是原子的,事务包含的所有操作要么全部成功,要么全部不执行。一旦有操作失败,则需要回退状态到执行事务之前;
  • Consistency:数据库的状态在事务执行前后的状态是一致的和完整的,无中间状态。即只能处于成功事务提交后的状态;
  • Isolation:各种事务可以并发执行,但彼此之间互相不影响。按照标准 SQL 规范,从弱到强可以分为未授权读取、授权读取、可重复读取和串行化四种隔离等级;
  • Durability:状态的改变是持久的,不会失效。一旦某个事务提交,则它造成的状态变更就是永久性的。

延伸-BASE与CAP

BASE原则

与 ACID 相对的一个原则是 eBay 技术专家 Dan Pritchett 提出的 BASE(Basic Availability,Soft-state,Eventual Consistency)原则。BASE 原则面向大型高可用分布式系统,主张牺牲掉对强一致性的追求,而实现最终一致性,来换取一定的可用性。

  • 基本可用(Basically Available):系统保证在出现故障或者异常情况时,仍然能够保证基本的可用性,即系统能够正常响应用户请求,但是可能会出现一些数据的不一致或者错误。
  • 软状态(Soft State):系统中的数据状态不需要是强一致性的,即允许数据在一段时间内处于中间状态,而不是必须保证数据在任何时间点都是完全一致的。这样可以提高系统的可扩展性和容错性。
  • 最终一致性(Eventual Consistency):系统保证最终会达到一致性状态,但是在数据更新后,不同节点之间可能存在一段时间的数据不一致。这种不一致是暂时的,随着时间的推移会逐渐被修复,直到所有节点都达到一致性状态。

BASE的设计理念是为了满足大规模分布式系统的需求,它与ACID的设计理念不同,不要求在所有情况下都严格保证数据的一致性,而是允许在某些情况下牺牲一部分的一致性,以换取更高的可用性和可扩展性。但是,在实际的应用中,需要根据具体的业务需求和系统特点,选择适合的数据管理系统设计理念,综合考虑数据的一致性、可用性和可扩展性等方面的因素。

CAP

CAP是指分布式系统中三个基本特性的缩写:

  • 一致性(Consistency):所有节点在同一时间看到的数据是一致的,即所有节点都具有相同的数据副本。在分布式系统中,这意味着在进行更新操作之后,所有节点必须最终达到一致的状态。
  • 可用性(Availability):系统能够在任何时间点正常响应用户请求,即系统的服务不会因为某些异常情况而停止响应。
  • 分区容错性(Partition tolerance):分布式系统能够在网络分区或者节点故障的情况下继续运行。这意味着系统在遇到网络分区或者节点故障时,仍然能够保证数据的一致性和可用性。

CAP理论指出,在一个分布式系统中,最多只能同时满足其中的两个特性,而不能同时满足所有三个特性。因此,当系统遇到网络分区或者节点故障时,分布式系统需要做出权衡,选择满足一致性和可用性中的哪一个特性,从而保证系统的正确性和可靠性。如果选择了分区容错性,那么系统可能会出现数据不一致的情况;如果选择了一致性和可用性,那么系统可能会出现停机和延迟响应等问题。

在实际的分布式系统中,需要根据具体的业务需求和系统特点,选择适合的CAP特性,综合考虑数据的一致性、可用性和容错性等方面的因素。

数据库并发事务存在问题

脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)都是数据库中的并发控制问题,主要影响事务的隔离性。

  • 脏读(Dirty read):指一个事务读取了另一个事务还未提交的未经提交的数据。如果另一个事务回滚,那么第一个事务读取的数据就是无效的,这可能会导致数据的不一致性。
  • 不可重复读(Non-repeatable read):指一个事务在读取同一行记录时,不同时间内读取到的数据不一致。这是因为在两次读取之间,另一个事务修改或删除了该行记录。
  • 幻读(Phantom read):指一个事务在读取一组记录时,第二次读取时发现多了或少了一些记录。这是因为在两次读取之间,另一个事务插入了一些记录或者删除了一些记录。

脏读、不可重复读、幻读都是由于并发事务之间的竞争而导致的问题,可以通过加锁或者使用更高级别的隔离级别来解决。

数据库隔离级别

SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

  • 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

隔离级别解决并发问题

image.png

数据库默认隔离级别

MYSQL

默认隔离级别是可重复读

mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.01 sec)

mysql> show global variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)

延伸:SESSION隔离级别是指在一个 MySQL 会话中,可以通过设置隔离级别来控制该会话中的事务隔离级别。在同一个 MySQL 实例中,不同会话可以设置不同的隔离级别。可以使用以下命令来设置 SESSION 隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL ;

其中,isolation_level 表示要设置的隔离级别,可以是 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE。

ORACLE

Oracle默认的隔离级别是读已提交(Read Committed)

一条查询SQL执行过程

SQL语句执行整体流程如下,在前面的整体架构图中也介绍过

image.png

客户端

客户端主要是通过MYSQL驱动来和数据库进行通信,MySQL 驱动是一种软件组件,用于在应用程序和 MySQL 数据库之间建立连接,以便应用程序可以访问和操作 MySQL 数据库。MySQL 驱动通常有以下几种类型:
JDBC驱动(Java Database Connectivity)、ODBC驱动(Open Database Connectivity)、Connector/C 驱动等
客户端需要频繁使用数据库连接,采用池化技术-数据库连接池来进行管理:维护一定的连接数,方便系统获取连接,使用就去池子中获取,用完放回去就可以了,我们不需要关心连接的创建与销毁,也不需要关心线程池是怎么去维护这些连接的。

image.png
常见的数据库连接池有 Druid、C3P0、DBCP,后面有一节专门讲述

连接器

连接器负责跟客户端建立连接、获取权限、维持和管理连接。
命令如下:

/home/mysql/mysql3306/bin/mysql -uroot -p -S /home/mysql/mysql3306/mysqld.sock

连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。

  • 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。

划重点:一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

show processlist 可以查看当前连接

mysql> show processlist;
+----+------+---------------------------+------+---------+------+----------+------------------+
| Id | User | Host                      | db   | Command | Time | State    | Info             |
+----+------+---------------------------+------+---------+------+----------+------------------+
|  2 | root | localhost                 | NULL | Query   |    0 | starting | show processlist |
|  8 | root | TOBY-HYW.mshome.net:24708 | NULL | Sleep   |   27 |          | NULL             |
|  9 | root | TOBY-HYW.mshome.net:24709 | NULL | Sleep   |   27 |          | NULL             |
+----+------+---------------------------+------+---------+------+----------+------------------+
3 rows in set (0.00 sec)

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

mysql> show variables like 'wait_timeout';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wait_timeout  | 28800 |
+---------------+-------+
1 row in set (0.01 sec)

建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。
MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。解决此问题:

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

    命中则直接结束,一般不使用。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。query_cache_type(OFF|ON|DEMAND 按需,使用select SQL_CACHE * from T where ID=10显示指定)控制 默认是关闭的

    mysql> show variables like 'query_cache_type';
    +------------------+-------+
    | Variable_name    | Value |
    +------------------+-------+
    | query_cache_type | OFF   |
    +------------------+-------+
    

    MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

    分析器

    词法分析、语法分析
    如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒

    优化器

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

    执行器

    MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。

    开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误,
    如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

    引擎层

    提供接口给执行层使用,分为多个执行引擎,后面单独讲述

    问题解答

    问题1

    如果表 T 中没有字段 k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?

    答案:分析器

    一条更新SQL执行过程

    更新SQL和查询SQL在server层是一样的(在查询缓存时将缓存失效调),在引擎层不一样,更新流程还涉及两个重要的日志模块

    参考
    1.一条SQL语句是如何执行的
    2.MySQL - 一条 SQL 的执行过程详解

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论