我们在学习任何知识的时候,脑子里面一定要有自己的脑图,能够构建出Mysql的工作的流程图,这不仅能够加深我们的记忆而且也有助于我们调优,因为我们知道流程之后,就能知道在哪里耗费时间,进而优化。下面展示了Mysql的逻辑架构图。
MySQL的逻辑架构分为三层:连接层、服务层和存储引擎层,连接层主要负责连接处理、授权认证、安全防护等,服务层用于处理核心服务,如标准的SQL接口、查询解析、SQL优化和统计、全局的和引擎依赖的缓存与缓冲器等等, 存储引擎层负责实际的MySQL数据的存储与提取,服务器通过API与存储引擎进行通信1,MySQL最重要、最与众不同的特性就是它的存储引擎架构,这种架构将查询处理、其他系统任务、数据的存储与提取三部分分离
MySQL的优化和执行是指MySQL在接收到用户的SQL语句后,如何对其进行解析、优化和执行的过程,包括重写查询,决定表的读取顺序,以及选着合适的索引。MySQL的优化和执行可以分为以下几个步骤:
并发控制:只要我们有多个sql语句需要再同一时刻修改数据,就会产生这个问题。
目前Mysql 主要在两个层面的并发控制。分别为服务层和存储引擎层。下面主要讲解服务层的并发控制
服务层并发控制:服务层是 MySQL 服务器的公共部分,它负责处理所有客户端的连接、查询、事务、锁等。服务层提供了一些通用的并发控制机。在 MySQL 中,服务层负责处理客户端请求,解析 SQL 语句,执行查询计划等。服务层的并发控制主要通过锁机制来实现,确保多个客户端之间的操作不会相互干扰。
共享锁(Shared Locks): 允许多个事务同时持有锁,适用于读操作。多个事务可以同时读取相同的数据,而不会造成冲突。
排他锁(Exclusive Locks): 一次只允许一个事务持有锁,适用于写操作。当一个事务持有排他锁时,其他事务不能同时持有任何锁。
行级锁(Row-level Locks): 锁定表中的行而不是整个表,提高并发性。MySQL 使用行级锁来支持更细粒度的并发控制。
死锁检测和超时处理: MySQL 的服务层实现了死锁检测机制,以及设置锁的超时时间,避免因为死锁导致系统长时间阻塞。
事务:事务是一组逻辑上相关的 SQL 语句,要么全部执行,要么全部不执行。事务可以保证数据的一致性和完整性,以及在出现故障时恢复数据。MySQL 支持不同的事务隔离级别,用于控制事务之间的可见性和并发冲突。想了解事务,一定要了解ACID特性
原子性(Atomicity): 事务是一个原子操作单元,不可分割。事务中的所有操作要么全部执行成功,要么全部失败回滚,不存在部分执行的情况。
一致性(Consistency): 事务在执行前后,数据库从一个一致的状态变为另一个一致的状态。事务执行的结果必须使数据库从一个合法的状态转变为另一个合法的状态。
隔离性(Isolation): 并发执行的事务之间是相互隔离的,一个事务的执行不应影响其他事务的执行。隔离性可以通过事务隔离级别来控制,包括读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
持久性(Durability): 一旦事务提交,其结果应该是永久性的,即使发生了系统故障,事务的结果也不应该丢失。通常通过将事务的结果写入事务日志,以便在需要时进行恢复。
在 SQL 中,通过 BEGIN TRANSACTION、COMMIT、ROLLBACK 等语句来定义和控制事务。例如:
sql
BEGIN TRANSACTION; -- 事务开始
-- 执行一系列 SQL 操作
COMMIT; -- 提交事务
-- 或者
ROLLBACK; -- 回滚事务
我相信大家已经了解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
下面是事务各个级别对应可能会出现的问题。
我们可以看到每一个隔离级别都有幻读,**那么mysql是怎么解决幻读的呢?**在InnoDB中使用的是多版本控制 :MVCC来解决幻读问题,等下章节将会讲解
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | Yes | Yes | Yes |
读提交(READ COMMITTED)) | No | Yes | Yes |
可重复读(REPEATABLE READ)) | No | No | Yes |
串行化(SERIALIZABLE)) | No | No | No |
事务的使用场景包括需要保证数据一致性的多个操作,如转账操作、库存管理等。在并发操作下,事务管理也变得非常重要,以确保多个用户或应用程序可以同时访问数据库而不破坏数据的完整性。
读未提交(Read Uncommitted):我们开两个窗口,隔离级别设置为 UNCOMMITTED。 原始的balance都为零。我们的事务都不进行提交,看是否能够读取到update后的数据
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
start transaction ;
select * from accounts where account_id =1;
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
读提交(Read Committed):读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了,我们开两个窗口,隔离级别设置为 Committed,然后开两个窗口,还是对事务都不提交操作。此时的余额还是为零
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED ;
start transaction ;
#第一次执行
select * from accounts where account_id =1;
#第二次执行
select * from accounts where account_id =1;
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
可重复读(Repeatable Read):我们开两个窗口,隔离级别设置为 Committed,然后开两个窗口,分别进行操作。此时的余额还是为零
SET session TRANSACTION ISOLATION LEVEL Repeatable Read ;
start transaction ;
# 第一次进行读操作
select * from accounts where account_id =1;
# 第二次进行读操作
select * from accounts where account_id =1;
SET session TRANSACTION ISOLATION LEVEL Repeatable Read;
start transaction;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
commit ;
串行化(Serializable):串行化是4种事务隔离级别中等级最高的,解决了脏读、可重复读、幻读的问题,但是性能最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。
参考链接 1。
知乎木木 ↩︎