mysql 进阶(一):整体逻辑架构

mysql 进阶(一):整体逻辑架构

    • 1.1 Mysql 逻辑架构
    • 1.2 Mysql 优化和执行
    • 1.3 Mysql 的并发控制(服务层)
    • 1.3.1 隔离级别和实现原理
    • 1.3.2 隔离级别实操

1.1 Mysql 逻辑架构

   我们在学习任何知识的时候,脑子里面一定要有自己的脑图,能够构建出Mysql的工作的流程图,这不仅能够加深我们的记忆而且也有助于我们调优,因为我们知道流程之后,就能知道在哪里耗费时间,进而优化。下面展示了Mysql的逻辑架构图。

                     mysql 进阶(一):整体逻辑架构_第1张图片

   MySQL的逻辑架构分为三层:连接层、服务层和存储引擎层,连接层主要负责连接处理、授权认证、安全防护等,服务层用于处理核心服务,如标准的SQL接口、查询解析、SQL优化和统计、全局的和引擎依赖的缓存与缓冲器等等, 存储引擎层负责实际的MySQL数据的存储与提取,服务器通过API与存储引擎进行通信1,MySQL最重要、最与众不同的特性就是它的存储引擎架构,这种架构将查询处理、其他系统任务、数据的存储与提取三部分分离

1.2 Mysql 优化和执行

  MySQL的优化和执行是指MySQL在接收到用户的SQL语句后,如何对其进行解析、优化和执行的过程,包括重写查询,决定表的读取顺序,以及选着合适的索引。MySQL的优化和执行可以分为以下几个步骤:

  1. 连接器:负责与客户端建立连接,获取权限,维持和管理连接。
    查询缓存:如果开启了查询缓存,如果是查询语句,MySQL 就会先去查询缓存里查找缓存数据,看看之前有没有执行过这一条命令,这个查询缓存是以 key-value 形式保存在内存中的,key 为 SQL 查询语句的哈希值,value 为 SQL 语句查询的结果。如果有则直接返回结果,否则继续后续的步骤。目前查询缓存8.0已经弃用,因为如果查询表中任何一个字段进行了修改,就会删除缓存,如果对表的操作很频繁,那么对这个查询缓存就很鸡肋了。
  2. 解析器:负责对SQL语句进行词法和语法分析,生成解析树,他会把sql语句进行关键字分割,然后按照树的顺序执行操作。
    优化器:负责对解析树进行优化,选择最优的执行计划,如表的连接顺序,索引的使用等。
  3. 执行器:负责根据优化器生成的执行计划,调用存储引擎的API,进行数据的读取和操作,返回结果集。
    mysql 进阶(一):整体逻辑架构_第2张图片

1.3 Mysql 的并发控制(服务层)

  并发控制:只要我们有多个sql语句需要再同一时刻修改数据,就会产生这个问题。
目前Mysql 主要在两个层面的并发控制。分别为服务层和存储引擎层。下面主要讲解服务层的并发控制

  服务层并发控制:服务层是 MySQL 服务器的公共部分,它负责处理所有客户端的连接、查询、事务、锁等。服务层提供了一些通用的并发控制机。在 MySQL 中,服务层负责处理客户端请求,解析 SQL 语句,执行查询计划等。服务层的并发控制主要通过锁机制来实现,确保多个客户端之间的操作不会相互干扰。

共享锁(Shared Locks): 允许多个事务同时持有锁,适用于读操作。多个事务可以同时读取相同的数据,而不会造成冲突。

排他锁(Exclusive Locks): 一次只允许一个事务持有锁,适用于写操作。当一个事务持有排他锁时,其他事务不能同时持有任何锁。

行级锁(Row-level Locks): 锁定表中的行而不是整个表,提高并发性。MySQL 使用行级锁来支持更细粒度的并发控制。

死锁检测和超时处理: MySQL 的服务层实现了死锁检测机制,以及设置锁的超时时间,避免因为死锁导致系统长时间阻塞。

   事务:事务是一组逻辑上相关的 SQL 语句,要么全部执行,要么全部不执行。事务可以保证数据的一致性和完整性,以及在出现故障时恢复数据。MySQL 支持不同的事务隔离级别,用于控制事务之间的可见性和并发冲突。想了解事务,一定要了解ACID特性

  1. 原子性(Atomicity): 事务是一个原子操作单元,不可分割。事务中的所有操作要么全部执行成功,要么全部失败回滚,不存在部分执行的情况。

  2. 一致性(Consistency): 事务在执行前后,数据库从一个一致的状态变为另一个一致的状态。事务执行的结果必须使数据库从一个合法的状态转变为另一个合法的状态。

  3. 隔离性(Isolation): 并发执行的事务之间是相互隔离的,一个事务的执行不应影响其他事务的执行。隔离性可以通过事务隔离级别来控制,包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

  4. 持久性(Durability): 一旦事务提交,其结果应该是永久性的,即使发生了系统故障,事务的结果也不应该丢失。通常通过将事务的结果写入事务日志,以便在需要时进行恢复。

  在 SQL 中,通过 BEGIN TRANSACTION、COMMIT、ROLLBACK 等语句来定义和控制事务。例如:

sql
BEGIN TRANSACTION; -- 事务开始
-- 执行一系列 SQL 操作
COMMIT; -- 提交事务
-- 或者
ROLLBACK; -- 回滚事务

1.3.1 隔离级别和实现原理

  我相信大家已经了解ACID特性了,那么下面我将深入的讲解隔离性(Isolation),隔离级别是指数据库在处理多个事务同时访问或修改数据时,如何保证数据的一致性和隔离性的设置。不同的隔离级别有不同的并发性能和数据可靠性。SQL 标准定义了四种隔离级别,分别是:

读未提交(Read Uncommitted):这是最低的隔离级别,它允许一个事务读取另一个事务未提交的数据,也称为“脏读”。这种级别会导致很多问题,如不可重复读、幻读、丢失更新等。一般很少使用这种级别,不建议使用,因为他的性能提升不了多少。

读提交(Read Committed):这是一种常用的隔离级别,它只允许一个事务读取另一个事务已提交的数据,可以避免脏读。但是,这种级别仍然可能出现不可重复读、幻读等问题。这种级别适合一些对数据一致性要求不太高的应用。

可重复读(Repeatable Read):这是 MySQL 的默认隔离级别,它保证了一个事务在执行期间看到的数据,总是和这个事务在启动时看到的数据是一致的,可以避免脏读和不可重复读。但是,这种级别仍然可能出现幻读的问题。这种级别适合一些对数据一致性要求较高的应用。

串行化(Serializable):这是最高的隔离级别,它要求事务串行执行,也就是说,同一时刻只能有一个事务在执行。这种级别可以避免所有的并发问题,但是并发性能也最差。这种级别一般只用于一些特殊的场景,如分布式事务或者故障排查。

  你可以使用 SET TRANSACTION ISOLATION LEVEL 语句来设置 MySQL 的隔离级别,你可以选择全局、会话或者单个事务的范围。你可以使用 SHOW VARIABLES LIKE ‘%isola%’ 语句来查看当前的隔离级别

  让我们考虑一个简单的场景,一个银行数据库,其中包含两个表:accounts 存储账户信息,transactions 存储交易记录。我们将演示如何使用 SQL 进行事务操作,并说明不同隔离级别下的行为。

-- 创建表
CREATE TABLE accounts (
    account_id INT PRIMARY KEY,
    account_name VARCHAR(255) NOT NULL,
    balance DECIMAL(10, 2) NOT NULL
);

CREATE TABLE transactions (
    transaction_id INT PRIMARY KEY,
    account_id INT,
    amount DECIMAL(10, 2) NOT NULL,
    transaction_type VARCHAR(10) NOT NULL,
    transaction_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);

-- 插入示例数据
INSERT INTO accounts (account_id, account_name, balance) VALUES
(1, 'Alice', 1000.00),
(2, 'Bob', 500.00);

-- 示例事务:从 Alice 账户向 Bob 账户转账 200.00
BEGIN;

-- 查询 Alice 账户余额
SELECT * FROM accounts WHERE account_name = 'Alice';

-- 查询 Bob 账户余额
SELECT * FROM accounts WHERE account_name = 'Bob';

-- 转账操作
UPDATE accounts SET balance = balance - 200.00 WHERE account_name = 'Alice';
UPDATE accounts SET balance = balance + 200.00 WHERE account_name = 'Bob';

-- 记录交易
INSERT INTO transactions (account_id, amount, transaction_type) VALUES
(1, -200.00, 'TRANSFER_OUT'),
(2, 200.00, 'TRANSFER_IN');

-- 提交事务
COMMIT;

-- 查看最终账户余额和交易记录
SELECT * FROM accounts;
SELECT * FROM transactions;

  在这个例子中,我们创建了两个表 accounts 和 transactions,并进行了一个简单的转账事务。注意事务是从 BEGIN 开始,通过 COMMIT 提交,或通过 ROLLBACK 进行回滚。这确保了转账过程中的一致性。

  针对隔离级别的例子,你可以在不同的事务中执行读取和更新操作,然后观察在不同隔离级别下的现象。注意在修改隔离级别的时候一定要检查自己的版本,在旧版本中使用tx_isolation,新版本已经弃用了,旧版本也就是5.x的变量才是tx_isolation,新版本(8.x)的系统变量改成transaction_isolation 。其中作用于可以是 SESSION 或者 GLOBAL,GLOBAL 是全局的,而 SESSION 只针对当前回话窗口。

-- 设置隔离级别
SET [作用域] TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 执行查询操作
SELECT * FROM accounts WHERE account_name = 'Alice';

-- 打开另一个会话,执行更新操作
UPDATE accounts SET balance = balance - 100.00 WHERE account_name = 'Alice';

-- 回到第一个会话,再次执行查询
SELECT * FROM accounts WHERE account_name = 'Alice';

-- 重复上述步骤,将隔离级别更改为 READ COMMITTED、REPEATABLE READ、SERIALIZABLE

  下面是事务各个级别对应可能会出现的问题。

  1. 不可重复读(Uncommitted Read):不可重复读指的是在一个事务中,同一查询在事务执行过程中的两次执行之间,由于其他事务的提交导致了数据的变化,从而导致同一查询的结果不一致。
  2. 脏读(Dirty Read): 脏读指的是一个事务读取了另一个事务尚未提交的数据,事务A修改某行数据,但尚未提交。事务B读取了事务A未提交的数据。如果事务A最终回滚,那么事务B读取到的数据实际上是无效的、脏的数据。
  3. 幻读(Phantom Read):幻读指的是在一个事务中,由于其他事务的插入或删除操作,导致同一个查
  4. 询在事务执行过程中返回不同数量的行。事务A查询某个范围的数据。事务B在这个范围内插入或删除了一些数据。事务A再次查询相同的范围,结果集发生了变化,出现了"幻影"行

  我们可以看到每一个隔离级别都有幻读,**那么mysql是怎么解决幻读的呢?**在InnoDB中使用的是多版本控制 :MVCC来解决幻读问题,等下章节将会讲解

隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED) Yes Yes Yes
读提交(READ COMMITTED)) No Yes Yes
可重复读(REPEATABLE READ)) No No Yes
串行化(SERIALIZABLE)) No No No

  事务的使用场景包括需要保证数据一致性的多个操作,如转账操作、库存管理等。在并发操作下,事务管理也变得非常重要,以确保多个用户或应用程序可以同时访问数据库而不破坏数据的完整性。

1.3.2 隔离级别实操

读未提交(Read Uncommitted):我们开两个窗口,隔离级别设置为 UNCOMMITTED。 原始的balance都为零。我们的事务都不进行提交,看是否能够读取到update后的数据

  1. 实现读取操作
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
start transaction ;
select * from accounts where account_id =1;
  1. 实现更新操作;在此事务没有提交前,这列数据不能被修改
SET session  TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
  1. 结果: 此时我们可以看出,成功的读取到了没有提交事务的数据。其实就是没有加锁,所以他的等级级别最低,有读到脏数据的问题。如果此时更新操作出问题了,进行回滚了,会出现什么问题? 那么你此时读取的余额是不是多出100元,我现在拿到这100元去消费,但其实余额是没有钱的,这样的设计是肯定不行的。所以一般工作中不会用到这个隔离级别。读的是脏数据原表并没有被修改 注意此隔离级别下读写操作可以同时进行,但写写操作无法同时进行。与此同时,该隔离级别下只会使用行级别的记录锁,并不会用间隙锁
    mysql 进阶(一):整体逻辑架构_第3张图片

读提交(Read Committed):读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了,我们开两个窗口,隔离级别设置为 Committed,然后开两个窗口,还是对事务都不提交操作。此时的余额还是为零

  1. 实现读取操作:事务A
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED ;
start transaction ;
#第一次执行
select * from accounts where account_id =1;
#第二次执行
select * from accounts where account_id =1;
  1. 实现更新操作:事务B
SET session  TRANSACTION ISOLATION LEVEL READ COMMITTED;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
  1. 结果:先执行事务A,然后在执行事务B,然后在执行事务A (就执行查询语句即可)。结果就是读取到的数据是零,因为事务B并没有提交。然后我们在事务B中执行commit提交事务,再次在事务A中查询余额,你会发现余额更新了,加了100块钱,但是你会发现在事务A中读取的两次数据都不一致,
    这就会出现不可重复读。
    读以提交还是只有一个行级别的记录锁,并没有间隙锁。看到这里,你会发现「读已提交」和「读未提交」非常相似。那么它们具体有啥区别呢?其实他们的最大区别,就是「读已提交」解决了脏读的问题。

可重复读(Repeatable Read):我们开两个窗口,隔离级别设置为 Committed,然后开两个窗口,分别进行操作。此时的余额还是为零

  1. 实现读操作 :事务A
SET session TRANSACTION ISOLATION LEVEL Repeatable Read ;
start transaction ;
# 第一次进行读操作
select * from accounts where account_id =1;
# 第二次进行读操作
select * from accounts where account_id =1;
  1. 实现写操作:事务B
SET session  TRANSACTION ISOLATION LEVEL Repeatable Read;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
commit ;
  1. 结果:我们可以看出,即使你事务B进行了提交,但是在事务A这里我依然读取的和第一次的一样,只要事务A不提交,那么他读取1万次,数据也是一样的。但是此时就会出现问题,假如我在事务B中执行插入id = 3的数据,此时我在事务A没有读取到id = 3 的数据,然后我也在事务A进行了插入id = 3 的数据,这是不是就出现问题了,这个问题就是幻读 。mysql怎么解决幻读的呢?其实是通过mvcc和间隙锁解决的,我们在下一章节进行讲解。对于「可重复读」隔离级别来说,会使用记录锁、间隙锁和 Next-Key 锁

mysql 进阶(一):整体逻辑架构_第4张图片

串行化(Serializable):串行化是4种事务隔离级别中等级最高的,解决了脏读、可重复读、幻读的问题,但是性能最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。

参考链接 1


  1. 知乎木木 ↩︎

你可能感兴趣的:(mysql,python,java)