【MySQL学习】事务管理(Transaction)

news2024/11/19 12:40:11

文章目录

  • 一、事务的基本认识
    • 1.1 事务的基本概念
    • 1.2 事务的基本属性
    • 1.3 支持事务的存储引擎
  • 二、为什么要有事务
  • 三、事务的基本操作
    • 3.1 事务的提交方式
    • 3.2 事务的操作案例
  • 四、事务的隔离级别
    • 4.1 对事务隔离性的初步理解
    • 4.2 四种隔离级别
    • 4.3 读未提交(Read Uncommitted)
    • 4.4 读提交(Read Committed)
    • 4.5 可重复读(Repeatable Read)
    • 4.6 串行化(Serializable)
    • 4.6 总结
  • 五、解决事务并发问题
    • 5.1 脏读(Dirty Read)
    • 5.2 不重复读(Non-repeatable Read)
    • 5.3 幻读(Phantom Read)


一、事务的基本认识

1.1 事务的基本概念

  • 简单来说,事务可以看作是一组数据操作语言(DML)语句的组成,这些语句在逻辑上具有强烈的相关性。事务的目的是确保这组操作要么全部成功执行,要么全部失败回滚,以保持数据库的一致性

  • 在事务中,如果其中任何一条语句执行失败,整个事务将会被回滚,即之前的操作都会被撤销,数据库状态回到事务开始之前的状态。只有当所有的操作都成功执行时,事务才会被提交,使得之前的操作生效并永久保存在数据库中。

  • 事务的使用可以确保数据库操作的完整性和可靠性,特别是在需要多个操作同时生效或者需要对数据进行一系列关联性修改时非常有用。通过将相关的操作组合在一个事务中,可以确保数据在多个操作之间保持一致,同时提供了对数据库的回滚和提交的控制能力。

1.2 事务的基本属性

事务具有以下四个基本属性,通常被称为 ACID 特性

  • 原子性(Atomicity):事务被视为一个不可分割的原子操作。要么所有的操作都成功执行,将更改永久保存到数据库中,即事务被提交;要么如果任何一个操作失败,所有的操作都将被回滚,撤销对数据库的修改,即事务被中止。原子性确保了事务的完整性,即要么所有的操作都生效,要么都不生效。

  • 一致性(Consistency):事务执行前后,数据库的状态必须保持一致。这意味着事务开始之前的数据库状态应该满足所有的约束条件、完整性规则和触发器,事务结束后,数据库状态也应该满足这些规则。如果事务执行过程中违反了一致性规则,事务将会被回滚,以保持数据库的一致性。

  • 隔离性(Isolation):事务的隔离性指的是每个事务的执行都应该与其他事务相互隔离,使得每个事务感觉不到其他事务的存在。事务的隔离性可以防止并发执行的事务相互干扰,避免数据的不一致性和并发问题,保证事务的独立性和正确性。

  • 持久性(Durability):一旦事务提交,其所做的修改将会永久保存在数据库中,即使在系统故障或重启后也不会丢失。持久性确保了事务的持久效果,保证数据的可靠性和持久性。

这些事务的基本属性一起确保了数据库操作的可靠性、一致性和持久性。当所有的属性都满足时,可以说事务是正确执行的。数据库管理系统提供了机制来确保事务的ACID属性,以支持可靠的数据处理和并发控制。

1.3 支持事务的存储引擎

在MySQL数据库中,以下存储引擎支持事务:

  • InnoDBInnoDB 是MySQL的默认和最常用的存储引擎。它提供了完整的事务支持,支持ACID属性,以及多版本并发控制(MVCC)来实现并发控制和隔离性。

  • NDB(也称为MySQL Cluster):NDB是一个基于内存的存储引擎,主要用于MySQL集群环境。它支持完整的事务支持,并提供高可用性和可伸缩性。

  • XtraDBXtraDBInnoDB存储引擎的一个改进版本,它提供了更好的性能和可扩展性,并保持了InnoDB的事务支持。

除了这些常见的存储引擎,MySQL还支持其他存储引擎,如MyISAMMemoryArchive等,但它们在事务支持方面有限或不支持事务。因此,如果需要使用事务,请确保选择支持事务的存储引擎,如InnoDB

查看数据库的引擎:show engines\G

二、为什么要有事务

事务的存在有以下几个主要原因和重要性:

  • 数据一致性:事务提供了一种机制,确保数据库操作的一致性。在一个事务中,要么所有的操作都成功执行,要么全部回滚。这样可以避免数据在不同操作之间出现不一致的情况,保持数据库的完整性。

  • 并发控制:事务可以管理并发访问数据库的能力。通过并发控制机制,事务可以保证多个并发执行的事务不会相互干扰或产生不一致的结果。它提供了隔离性,确保每个事务的操作在逻辑上相互隔离,避免了数据竞争和冲突。

  • 错误恢复:事务可以提供错误恢复的能力。如果在事务执行过程中发生错误,事务可以被回滚,撤销之前的操作,将数据库状态恢复到事务开始之前的状态。这样可以避免数据的损坏或不一致,确保系统的可靠性和可恢复性。

  • 原子性:事务具有原子性,即事务被视为一个不可分割的原子操作。这意味着要么所有的操作都成功执行,要么全部失败回滚。原子性确保了事务的完整性,避免了只执行了一部分操作导致的数据不一致问题。

  • 高效性:通过将多个相关的操作组合在一个事务中,可以减少与数据库的交互次数,提高效率。事务允许批量执行一组操作,减少了数据库的开销和资源消耗,提升了系统性能。

综上所述,事务是确保数据库操作的一致性、并发控制和错误恢复的关键机制。它提供了数据的一致性、可靠性和可恢复性,是建立可靠、高效的数据库应用的基础。

三、事务的基本操作

3.1 事务的提交方式

事务的提交方式主要有以下两种:

  • 自动提交(Auto Commit):在自动提交模式下,每个SQL语句都被视为一个独立的事务,并自动提交到数据库中。这意味着在执行每个SQL语句后,都会立即将其结果永久保存到数据库中,无需显式地执行提交语句。自动提交模式是一种默认的提交方式,在大多数数据库管理系统中,默认情况下处于自动提交模式

  • 手动提交(Manual Commit):在手动提交模式下,事务的提交需要显式地执行提交语句。在手动提交模式下,通过执行提交语句(如COMMIT)将事务的操作结果永久保存到数据库中。在手动提交模式下,可以在事务执行过程中进行回滚(ROLLBACK)或提交(COMMIT)操作,具有更细粒度的事务控制

查看事务的提交方式:

show variables like 'autocommit';


可以看到此时事务的提交方式是自动提交的。

SET 来改变 MySQL 的自动提交模式:

-- 关闭自动提交,及设置成手动提交
set autocommit = 0;

-- 设置事务为自动提交
set autocommit = 1;

3.2 事务的操作案例

为了便于演示,此时将 MySQL 的隔离级别设置为读未提交(Read Uncommitted),关于隔离级别,后文会有详解。

创建测试表

create table if not exists account(
	id int primary key,
	name varchar(50) not null default '',
	blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;

正常演示 — 验证事务的开始与回滚


首先使用start transaction开启事务,然后向表中插入数据,并在插入的数据之间设置savepoint保存点,通过语句rollback to point可以回滚到指定的保存点,如果直接rollback则会回滚到事务开始的状态。

非正常演示1 — 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)

终端A,使用begin开启事务,然后向表中插入一条数据,未提交事务就终止了该终端:

终端B的情况:

由于终端A的事务未提交就已经崩溃了,因此会自动回滚到事务开启前的状态。

非正常演示2 — 证明commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化

终端A开启事务,插入一条数据后提交事务,然后ctrl + \终止该终端。

此时终端B可以看到该事务插入的数据,及commit提交事务了,即使客户端崩溃,MySQL数据不会在受影响,因为已经持久化了。

非正常演示3 — 对比试验。证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响

终端A关闭事务的自动提交,开启事务,插入一条数据,然后直接终止终端:

终端B的情况:

通过以上实验可以证明,手动使用begin开启事务,与是否开启了事务的自动提交没有关系。即只要显示的使用命令开启了事务,那么这就是手动事务。

非正常演示4 — 证明单条 SQL 与事务的关系

实验一:

终端A关闭事务的自动提交,执行单条SQL,然后终止终端:

此时,终端B的情况:

实验二:

终端A,关闭事务的自动提交,执行单条SQL,然后提交事务,最后终止终端:

终端B的情况:


此时,由于终端A在崩溃前已经提交事务了,终端B可以看到插入的记录,因为其已经持久化了。最后得出结论是:如果关闭事务的自动提交,那么即使执行单条SQL,也需要手动提交事务

【总结】

  • 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是否设置set autocommit无关。
  • 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
  • 对于 InnoDB 每一条 SQL语言都默认封装成事务,自动提交。( select有特殊情况,因为MySQL 有 MVCC多版本并发控制 )
  • 从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
    那么隔离性?一致性?
    【事务操作的注意事项】
  • 如果没有设置保存点,也可以回滚,只能回滚到事务的开始,即直接使用 rollback(前提是事务还没有提交)。
  • 如果一个事务被提交了(commit),则不可以回退(rollback
  • 可以使用rollback to point选择回退到某个保存点。
  • InnoDB 支持事务, MyISAM 不支持事务。
  • 开始事务可以使 start transaction 或者 begin

四、事务的隔离级别

4.1 对事务隔离性的初步理解

简单来说,事务的隔离性就是指数据库管理系统(DBMS)可以确保在同时运行多个事务时,每个事务都能独立地执行,并且不会互相干扰。换句话说,一个事务的执行不应该影响到其他事务的执行,同时也不受其他事务的影响。

在事务隔离性中,每个事务都应该具有独立的执行空间和资源,如内存和CPU时间。这样可以避免一个事务修改了正在被其他事务使用的数据,从而导致数据的不一致性。DBMS通常通过锁定机制来实现隔离性,以确保事务之间不会互相干扰。这些锁可以是行级锁、页级锁或表级锁,具体实现取决于DBMS的架构和设计。

4.2 四种隔离级别

四种隔离级别指的是数据库管理系统(DBMS)中用于控制事务之间隔离性的四个级别,它们分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

1. 读未提交(Read Uncommitted)

  • 该隔离级别最低,允许一个事务读取其他事务尚未提交的数据。
  • 由于没有隔离性,就会导致很多并发问题,如脏读,幻读,不可重复读等,上面为了做实验方便,用的就是这个隔离性。

2. 读提交(Read Committed)

  • 该隔离级别要求一个事务只能读取其他已提交的事务所做的修改。
  • 事务之间通过加锁来实现隔离,避免了脏读,但仍可能出现不可重复读(Non-repeatable Read),即多次读取同一数据得到不同的结果。

3. 可重复读(Repeatable Read)

  • 该隔离级别要求一个事务在执行期间多次读取同一数据时,能够得到一致的结果。
  • 事务在读取数据时会对所涉及的数据进行加锁,避免了脏读和不可重复读,但仍可能出现幻读(Phantom Read),即在同一事务内部执行相同的查询语句得到不同的结果。
  • 该隔离级别是MySQL的默认隔离级别。

4. 串行化(Serializable)

  • 该隔离级别要求事务串行执行,即每个事务都必须等待前一个事务完成后才能开始执行。
  • 这是最高的隔离级别,可以避免脏读、不可重复读和幻读,但也降低了并发性能,因为事务需要串行执行。

每个隔离级别在提供隔离性的同时,也带来了不同的性能和并发控制开销。选择适当的隔离级别需要综合考虑应用的一致性需求、并发访问的频率和数据的可重复性等因素。

查看与设置隔离级别

查看隔离级别:

mysql> select @@global.tx_isolation; -- 查看全局隔离级别
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED      |
+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@session.tx_isolation; -- 查看当前会话隔离级别
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED       |
+------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select @@tx_isolation; -- 查看当前会话隔离级别
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

设置隔离级别:

-- 设置当前会话 或者 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL 
	{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

例如:

  1. 设置当前会话隔离性,另开启一个终端登录MySQL,发现只影响当前会话。

  1. 设置全局隔离性,另开启一个终端登录MySQL,发现会影响新会话,而当前会话需要重启才会变成设置的全局隔离级别。

【注意】
设置的全局隔离级别,会在mysqld服务重启后,恢复到可重复读(Repeatable Read)级别。

以下是对各种隔离级别的实验验证

4.3 读未提交(Read Uncommitted)

终端A设置全局隔离级别为Read Uncommitted,然后重启,并开启终端B。终端A开启事务,并插入一条数据。由于隔离级别设置为了Read Uncommitted,因此开启了事务的终端B可以看到其他终端未提交的事务。

一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读(dirty read)。

4.4 读提交(Read Committed)

设置隔离级别为Read Committed,终端A和B先后分别开启事务,可以发现在A修改数据但未提交事务之前,终端B的事务看不到修改的数据,而当终端A提交事务后,终端B的事务就能看到刚刚另一个事务修改的数据。

但是,此时终端B的事务还未提交,那么就造成了在同一个事务内进行同样的读取,在不同的时间段读取到的数据不一致,这种现象叫做不可重复读(non-reapeatable read)。

4.5 可重复读(Repeatable Read)

实验一:

设置隔离级别为Repeatable Read,终端A和B先后分别开启事务,然后终端A修改一条数据,然后再提交事务。

终端B的事务在终止A的事务修改数据的前后,以及终端A提交了事务之后分别查询一次表中的数据,发现都不能看到终端A的事务对数据的修改。而当终端B提交了自己的事务之后,就能看到了。

实验二:

设置隔离级别为Repeatable Read,终端A和B先后分别开启事务,然后终端A插入一条数据,然后提交事务。此时终端B的事务查询account表,却发现能看到终端A事务新增的数据。


因为,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据。因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题。就会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read)。

4.6 串行化(Serializable)

终端A和B先后分别开启事务,二者同时查询数据的时候不会串行化,如果此时终端A的事务执行修改操作,那么就会阻塞住:

此时,如果终端B提交了事务,那么终端A事务的更新操作才会被执行:


设置所有的隔离级别为Serializable,进行串行化,不会出现任何并发问题,但是只要串行化,效率很低,几乎完全不会被采用。

4.6 总结

  • 隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
  • 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了。
  • 幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样。
  • MySQL默认的隔离级别是可重复读,一般情况下不要修改。
  • 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大。

五、解决事务并发问题

5.1 脏读(Dirty Read)

脏读指的是一个事务读取了另一个事务尚未提交的数据。为了解决脏读问题,可以使用以下方法:

  • 使用读已提交(Read Committed)隔离级别:这个隔离级别要求一个事务只能读取已提交的数据,从而避免了脏读的情况。
  • 使用行级锁:当一个事务对某一行数据进行修改时,可以对该行数据加锁,其他事务在读取该行数据时需要等待锁释放,从而避免了脏读。

5.2 不重复读(Non-repeatable Read)

不可重复读指的是在一个事务内,多次读取同一数据时得到不同的结果。为了解决不可重复读问题,可以使用以下方法:

  • 使用可重复读(Repeatable Read)隔离级别:这个隔离级别要求一个事务在执行期间多次读取同一数据时,能够得到一致的结果,避免了不可重复读的情况。
  • 使用行级锁或快照隔离:通过在事务读取数据时对相关数据进行加锁或创建数据快照,确保其他事务对数据的修改不会影响到当前事务的读取结果。

5.3 幻读(Phantom Read)

幻读指的是在一个事务内,多次执行相同的查询语句得到不同的结果,通常是由于其他事务插入或删除了符合查询条件的数据所致。为了解决幻读问题,可以使用以下方法:

  • 使用串行化(Serializable)隔离级别:这个隔离级别要求事务串行执行,避免了并发事务对数据的插入和删除操作,从而避免了幻读。
  • 使用范围锁:当一个事务执行某个范围的查询时,可以对该范围的数据加锁,防止其他事务对该范围内的数据进行插入或删除,从而避免幻读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537270.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我能“C”——初阶结构体

目录 结构体 1.结构体的声明 1.1结构体的基础知识 1.2结构体的声明 1.3结构成员的类型 1.4结构体变量的定义和初始化 2. 结构体成员的访问 3.结构体传参 THE END 结构体 结构体类型的声明 结构体初始化 结构体成员访问 结构体传参 1.结构体的声明 1.1结构体的基础知…

单板硬件设计:存储器SD卡( NAND FLASH)

在单板设计中,无论是涉及到一个简易的CPU、MCU小系统或者是复杂的单板设计,都离不开存储器设计: 1、存储器介绍 存储器的分类大致可以划分如下: ROM和RAM指的都是半导体存储器,ROM在系统停止供电的时候仍然可以保持数…

防火墙可以“阻挡”黑客的进攻吗?

"防火墙"这个词大家应该都听说过或者应用过,每个人的电脑、手机几乎都会安装一些的主流的防火墙软件,工作的企事业单位网络里都会安装硬件防火墙。那么这些防火墙能阻挡住黑客的攻击吗? 一、首先我们需要知道防火墙的原理或者说主…

vue项目启动出现可选链?:操作符解析失败

问题描述:vue项目中引入了其他npm包,包中使用可选链操作符?:,本地npm run serve启动时,之前都正常,这次报错了,无法启动。 解决步骤(2步): 1. 安装:&#…

【算法思维】-- KMP算法

OJ须知: 一般而言,OJ在1s内能接受的算法时间复杂度:10e8 ~ 10e9之间(中值5*10e8)。在竞赛中,一般认为计算机1秒能执行 5*10e8 次计算。 时间复杂度取值范围o(log2n)大的离谱O(n)10e8O(nlog(n))10e6O(nsqrt(…

某神QQ机器人BOT搭建教程win系统

某神QQ机器人BOT搭建教程win系统 大家好我是艾西,今天跟大家分享的是某神qi鹅群机器人bot搭建方式以及详细的操作步骤。跟上艾西的节奏准备发车啦! 前言:(xxxx即为https)(zzz即为com) qi鹅群…

geoserver发布矢量切片服务理论与实战

geoserver发布矢量数据服务前几篇文章已经分享过了,但是在实际业务中,矢量数据shp文件有时候比较大,包含上百万完个点,发布完整的服务后,有时候,前端显示还是有点慢,毕竟是一次加载完成&#xf…

Verilog语法概述二:何为仿真?仿真可以在几个层面上进行?

Verilog 是一种用于数字逻辑电路设计的硬件描述语言,可以用来进行数字电路的仿真验证、时序分析、逻辑综合。 既是一种行为级(可用于电路的功能描述)描述语言又是一种结构性(可用于元器件及其之间的连接)描述语言。 …

day34_js

今日内容 零、 复习昨日 一、JS 零、 复习昨日 一、引言 1.1 JavaScript简介 JavaScript一种解释性脚本语言,是一种动态类型、弱类型、基于原型继承的语言,内置支持类型。它的解释器被称为JavaScript引擎,作为浏览器的一部分,广泛…

数据分类分级 数据识别-excel分类分级模版文件导入、解析

前面讲了数据分类分级 数据识别-实现部分敏感数据识别,本次针对模版导入展开,excel导入采用的是easyexcel 目录 easyexcel介绍easyexcel实战添加依赖读取数据监听器的实现数据读取方法读取结果上面图片是AI创作生成!如需咒语可私戳哦! easyexcel介绍 之前的excel导入解析…

全网最可”铐“最可“刑”的fiddler抓包教程

Fiddler 下载: https://telerik-fiddler.s3.amazonaws.com/fiddler/FiddlerSetup.exe 浏览器f12 选择“网络”,点选“保留日志” Fiddler 浏览器执行“去缓存刷新”:shiftf5 会话 即是 包 har文件在测试当中有什么作用?:h…

存储卡格式化后如何找回数据?一招轻松恢复数据

存储卡内存不足、存储卡中毒、存储卡损坏这几种情况都会导致我们把存储卡格式化操作,存储卡格式化后所有数据都会清空,这是众所皆知的,存储卡不小心格式化了怎么办? 其实是有方法恢复格式化的数据,我们可以通过专业的数…

香橙派4和树莓派4B构建K8S集群实践之五:端口公开访问配置

1. 应用场景说明 - 我们需用k8s集群提供开放特别的端口访问服务,如一些微服务端口 - 在团队开发环境中,通常要访问公共数据库,集群需提供统一的接口给大伙 接下来以实践四中设置的mariadb-galera为基础,公开端口3306&#xff0…

银豆信息张雪灿:钻石级合作伙伴的增长秘诀

编者按: 杭州银豆信息技术有限公司(简称“银豆”),是一家专注于云计算服务的高科技企业,目前已为2000家企业级客户提供了专业的行业解决方案, 与人民网、光大银行、长安汽车金融、vivo金融、浙江省农科院、淄博市大数…

django部署在Centos7+python3+apache上教程

django在本地环境非常好配置使用自带的web服务就可以了但是部署到服务器上再使用自带的web就不方便了 一般是配合nginx或apache来使用。 这篇文章主要是教如何搭配apache的 1.升级sqlite3(高版本django高版本不支持低版本sqlite3) #一定要在安装python…

[ACTF新生赛2020]easyre 题解

1.查壳 32位文件,加了UPX壳 2.手动脱壳 使用Ollydbg UPX是一个压缩壳,运行了UPX将我们要运行的已经压缩的程序解压,才是真正的程序入口点OEP 我们需要将跟着汇编代码,找到程序真正的入口点 使用ESP定律可以快速定位 按下F7&…

Git设置代理

有时会国内会因为github克隆速度非常慢,中途各种错误断开造成克隆项目失败,可以尝试设置代理解决。 1、http、https协议 //设置全局代理 //http git config --global https.proxy http://127.0.0.1:1080 //https git config --global https.proxy http…

图神经网络:(节点分类)在KarateClub数据集上动手实现图神经网络

文章说明: 1)参考资料:PYG官方文档。超链。 2)博主水平不高,如有错误还望批评指正。 3)我在百度网盘上传了这篇文章的jupyter notebook。超链。提取码8888。 文章目录 文献阅读:代码实操: 文献阅读: 参考文…

一个由“API未授权漏洞”引发的百万级敏感数据泄露

2023年4月的某一天,腾讯安全专家Leo正在为某家医院的重保防护做第一轮的安全风险排查。 医院的专用APP是外部网络访问最高的,也就是最大的风险敞口,需要重点排查。 Leo下载APP进行测试后,发现该医院存在一个严重的问题&#xff…