【MySQL 数据库】MySQL事务管理(二)

发布于:2025-08-05 ⋅ 阅读:(14) ⋅ 点赞:(0)

请添加图片描述


📝事务隔离级别

如何理解隔离性1

  • MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
  • 一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
  • 但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
  • 就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。
  • 数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
  • 数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别

隔离级别

  • 读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多
  • 并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
  • 可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
  • 串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)

隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。不过,我们目前现有这个认识就行,先关注上层使用。

查看与设置隔离性
以下是替换 tx_isolationtransaction_isolation 后的完整内容,保留原有逻辑和格式:

🌠 查看事务隔离级别

-- 查看
-- 查看全局隔离级别,替换 @@global.tx_isolation 为 @@global.transaction_isolation
mysql> SELECT @@global.transaction_isolation;  --查看全局隔离级别
+-------------------------------+
| @@global.transaction_isolation |
+-------------------------------+
| REPEATABLE-READ               |
+-------------------------------+
1 row in set, 1 warning (0.00 sec)

-- 查看会话(当前)隔离级别,替换 @@session.tx_isolation 为 @@session.transaction_isolation
mysql> SELECT @@session.transaction_isolation;  --查看会话(当前)隔离级别
+--------------------------------+
| @@session.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ                |
+--------------------------------+
1 row in set, 1 warning (0.00 sec)

-- 默认同上,替换 @@tx_isolation 为 @@transaction_isolation
mysql> SELECT @@transaction_isolation;           --默认同上
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set, 1 warning (0.00 sec)

-- 设置
-- 设置当前会话 or 全局隔离级别语法,语法本身无 tx_isolation ,无需替换,保持原逻辑
-- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

-- 设置当前会话隔离性,另起一个会话,看不多,只影响当前会话,语法本身无 tx_isolation ,无需替换,保持原逻辑
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)

验证会话隔离级别设置效果

-- 设置当前会话隔离性,另起一个会话,看不多,只影响当前会话,语法本身无 tx_isolation ,无需替换,保持原逻辑
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)

-- 查看全局隔离级别,替换 @@global.tx_isolation 为 @@global.transaction_isolation,全局隔离性还是RR 
mysql> SELECT @@global.transaction_isolation;  --全局隔离性还是RR
+-------------------------------+
| @@global.transaction_isolation |
+-------------------------------+
| REPEATABLE-READ               |
+-------------------------------+
1 row in set, 1 warning (0.00 sec)

-- 查看会话隔离级别,替换 @@session.tx_isolation 为 @@session.transaction_isolation,会话隔离性成为串行化 
mysql> SELECT @@session.transaction_isolation;  --会话隔离性成为串行化
+--------------------------------+
| @@session.transaction_isolation |
+--------------------------------+
| SERIALIZABLE                   |
+--------------------------------+
1 row in set, 1 warning (0.00 sec)

-- 默认同上,替换 @@tx_isolation 为 @@transaction_isolation 
mysql> SELECT @@transaction_isolation;           --同上
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| SERIALIZABLE           |
+-------------------------+
1 row in set, 1 warning (0.00 sec)

设置全局隔离级别及验证

-- 设置全局隔离性,另起一个会话,会被影响,语法本身无 tx_isolation ,无需替换,保持原逻辑
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

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

-- 查看会话隔离级别,替换 @@session.tx_isolation 为 @@session.transaction_isolation 
mysql> SELECT @@session.transaction_isolation;
+--------------------------------+
| @@session.transaction_isolation |
+--------------------------------+
| READ-UNCOMMITTED               |
+--------------------------------+
1 row in set, 1 warning (0.00 sec)

-- 默认同上,替换 @@tx_isolation 为 @@transaction_isolation 
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED        |
+-------------------------+
1 row in set, 1 warning (0.00 sec)

-- 注意,如果没有现象,关闭mysql客户端,重新连接。 

🌠 读未提交【Read Uncommitted】

--几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
--终端A
-- 设置隔离级别为 读未提交
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

--重启客户端

mysql> select @;
+-------------------------+
| @ |
+-------------------------+
| READ-UNCOMMITTED        |
+-------------------------+
1 row in set (0.00 sec)

mysql> select * from account;
ERROR 1046 (3D000): No database selected
mysql> select * from account;^C
mysql> use affairs;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from account;
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   100.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

mysql> begin;			--开启事务
Query OK, 0 rows affected (0.00 sec)	

mysql> update account set blance=123.0 where id=1;	--更新指定行
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> 

--没有commit哦!!!

mysql> select * from account;	-读到终端A更新但是未commit的数据[insert,delete同样]
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   100.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读
(dirty read)


--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   123.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

mysql> 

🌠 读提交【Read Committed】

环境准备(终端 A 操作)

-- 设置全局事务隔离级别为读已提交(read committed)
set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

-- 重启客户端后,查看 account 表初始数据
select * from account;  
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   123.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 手动开启事务,后续同步终端 B 事务操作
begin;  
Query OK, 0 rows affected (0.00 sec)

-- 更新张三的账户余额(事务内操作,未提交)
update account set blance=321.0 where id=1;  
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 提交事务,使更新对其他会话可见
commit;  
Query OK, 0 rows affected (0.01 sec)

终端 B 验证“不可重复读”现象

-- 手动开启事务,与终端 A 事务前后执行
begin;  
Query OK, 0 rows affected (0.00 sec)

-- 第一次查询(终端 A 未 commit 时),读取旧值
select * from account;  
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   123.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 终端 A commit 后,再次查询(同一事务内),读取新值  
-- 同一事务内,相同查询在不同时间得到不同结果,即“不可重复读(non repeatable read)”
select * from account;  
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

🌠可重复读【Repeatable Read】

以下是保持内容不变的分段整理,按操作场景和终端区分,便于阅读:

一、可重复读(REPEATABLE-READ)隔离级别下的更新操作演示

终端A操作

-- 设置全局隔离级别为可重复读(RR)
mysql> set global transaction isolation level repeatable read; -- 设置全局隔离级别RR
Query OK, 0 rows affected (0.01 sec)

-- 关闭终端重启

-- 查看当前隔离级别,确认已设置为RR
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |                                 -- 隔离级别RR
+-----------------+
1 row in set, 1 warning (0.00 sec)

-- 查看account表当前数据
mysql> select *from account;  -- 查看当前数据
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 开启事务(终端B同步开启事务)
mysql> begin;                 -- 开启事务,同步的,终端B也开始事务
Query OK, 0 rows affected (0.00 sec)

-- 更新id=1的记录
mysql> update account set blance=4321.0 where id=1; -- 更新数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 切换到终端B,查看另一个事务是否能看到

-- 提交事务
mysql> commit;  -- 提交事务
Query OK, 0 rows affected (0.00 sec)

-- 切换终端到终端B,查看数据。

终端B操作

-- 开启事务
mysql> begin; 
Query OK, 0 rows affected (0.00 sec)

-- 终端A事务提交前,查看数据(未更新)
mysql> select * from account; -- 终端A中事务 commit之前,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 终端A事务提交后,查看数据(仍未更新)
mysql> select * from account; -- 终端A中事务 commit 之后,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 可重复读特性说明
-- 可以看到,在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读!

-- 结束事务
mysql> commit;          -- 结束事务
Query OK, 0 rows affected (0.00 sec)

-- 事务结束后,查看最新数据
mysql> select * from account;  -- 再次查看,看到最新的更新数据
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

二、可重复读(REPEATABLE-READ)隔离级别下的插入操作与幻读问题

--------------------------------------------------------------------------------
-- 如果将上面的终端A中的update操作,改成insert操作,会有什么问题??

终端A操作

-- 查看account表当前数据
mysql> select *from account;
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 开启事务(终端B同步开启)
mysql> begin;           -- 开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)

-- 插入新记录
mysql> insert into account (id,name,blance) values(3, '王五', 5432.0);
Query OK, 1 row affected (0.00 sec)

-- 切换到终端B,查看另一个事务是否能看到

-- 提交事务
mysql> commit; -- 提交事务
Query OK, 0 rows affected (0.00 sec)

-- 切换终端到终端B,查看数据。

-- 终端A提交后,自身查看数据(已包含新记录)
mysql> select * from account;
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
|  3 | 王五   |  5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)

终端B操作

-- 开启事务
mysql> begin;  -- 开启事务
Query OK, 0 rows affected (0.00 sec)

-- 终端A提交前,查看数据(无新记录)
mysql> select * from account; -- 终端A commit前 查看
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 终端A提交后,查看数据(仍无新记录)
mysql> select * from account; -- 终端A commit后 查看
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 多次查看及幻读问题说明
mysql> select * from account; -- 多次查看,发现终端A在对应事务中insert的数据,在终端B的事务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-key锁(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

-- 结束事务
mysql> commit;  -- 结束事务
Query OK, 0 rows affected (0.00 sec)

-- 事务结束后,查看最新数据(包含新记录)
mysql> select * from account; -- 看到更新
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
|  3 | 王五   |  5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)

在这里插入图片描述

🌠串行化【serializable】

–对所有操作全部加锁,进行串行化,不会有问题,但是只要串行化,效率很低,几乎完全不会被采用

--终端A
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> select @;
+-----------------+
| @  |
+-----------------+
| SERIALIZABLE    |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> begin;                          --开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account; --两个读取不会串行化,共享锁
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
|  3 | 王五   |  5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)

mysql> update account set blance=1.00 where id=1; --终端A中有更新或者其他操作,会阻塞。直到终端B事务提交。
Query OK, 1 row affected (18.19 sec)
Rows matched: 1  Changed: 1  Warnings: 0

--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account; --两个读取不会串行化
+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |  4321.00 |
|  2 | 李四   | 10000.00 |
|  3 | 王五   |  5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)

mysql> commit; --提交之后,终端A中的update才会提交。
Query OK, 0 rows affected (0.00 sec)

在这里插入图片描述
总结:

  • 其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
  • 不可重复读的重点是修改和删除:同样的条件,你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增:同样的条件,第1次和第2次读出来的记录数不一样
  • 说明:mysq1默认的隔离级别是可重复读,━般情况下不要修改
  • 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大。
    在这里插入图片描述
    一致性(Consistency)
  • 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
  • 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。
  • 而技术上,通过AID保证C

🌉脏读(Dirty Read)

  • 定义:在一个事务中读取到了另一个事务未提交的数据 。简单来说,就是事务A读取了事务B已经修改但尚未提交的数据,之后若事务B回滚,那么事务A读取到的数据就是无效的、“脏”的。
  • 示例:假设有两个事务,事务T1和事务T2, 表account中有一条记录,id = 1balance(余额)为100元 。
    • 事务T1开启,执行UPDATE account SET balance = 200 WHERE id = 1;,此时balance在事务T1内被修改为200,但还未提交。
    • 事务T2开启,执行SELECT balance FROM account WHERE id = 1; ,读取到的balance值是200。
    • 接着事务T1发生错误回滚,balance恢复为100。但事务T2之前读取到的却是200,这就是脏读,T2读取到了未最终确定的数据。
  • 影响: 脏读可能导致数据处理错误,比如在金融系统中,基于脏读的数据进行转账计算,会导致账户金额计算错误,破坏数据的一致性和准确性。

🌉幻读(Phantom Read)

  • 定义:在一个事务中,多次执行相同的查询,在事务执行期间,由于其他事务插入了满足查询条件的新数据 ,导致每次查询的结果集不一样,就好像产生了幻觉,出现了“幻影”数据。幻读主要针对的是插入操作带来的数据不一致问题。
  • 示例:还是以account表为例,假设表中有两条记录,id分别为1和2 。
    • 事务T1开启,执行SELECT * FROM account WHERE id > 0; ,得到包含两条记录的结果集。
    • 事务T2开启并执行INSERT INTO account (id, balance) VALUES (3, 300); ,然后提交事务。
    • 事务T1再次执行SELECT * FROM account WHERE id > 0; ,发现结果集中多了一条id = 3的记录,与第一次查询结果不同,这就是幻读现象。
  • 影响:幻读会破坏事务的隔离性和一致性,例如在库存管理中,事务A查询出库存数量后准备进行修改,事务B插入了新的库存记录,事务A再次查询时库存数量发生变化,可能导致超卖等错误情况。

幻读:简单来说:

会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查这类问题,会查找出全新的记录,就如同产生了幻觉,这种现象,叫做幻读(phantom read)。
  • 本质
    同一事务内,多次查询相同条件,第一次查询到 N 条记录,后续查询却因其他事务插入数据,多出了新的记录(这些记录符合查询条件,但之前不存在),仿佛“幻觉般出现”,这就是幻读

MySQL 的特殊处理:RR 级别解决幻读

很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-key锁(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
  • MySQL 的与众不同
    多数数据库(如 Oracle )的 RR 级别无法解决幻读,但 MySQL 的 RR 级别通过 Next-key Lock(临键锁,包含“行锁 + 间隙锁” )解决了幻读

  • Next-key Lock 原理

    • 行锁:锁住已有记录,防止修改。
    • 间隙锁(GAP Lock):锁住“不存在的区间”,比如表中 id1、3,间隙锁会锁住 (1,3) 区间,防止其他事务插入 id=2 的数据。
    • 结合两者,MySQL 在 RR 级别下,能阻止其他事务插入“符合查询条件”的新数据,从而避免幻读

推荐阅读

https://www.jianshu.com/p/398d788e1083
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://www.cnblogs.com/aspirant/p/9177978.html

备注:
基本上,了解了上面的知识,在MySQL事务使用上,肯定没有问题。不过,这块设计很优秀,也是面试中可能
被问到的,一般学生,如果能说出上面的内容,就已经不错了。但是如果我们能更详细,更深入的谈论这个问
题,那么对我们的面试与学习肯定是大大的有帮助。
不过接下来的内容,会比较难一些,听的不明白,也没有太大问题。

—在RR级别的时候,多个事务的update,多个事务的insert,多个事
务的delete,是否会有加锁现象。
现象结果是,update,insert,delete之间是会有加锁现象的,但是select和这些操作是不冲突的。这就
是通过读写锁(锁有行锁或者表锁)+MVCC完成隔离性。

🚩总结

请添加图片描述


网站公告

今日签到

点亮在社区的每一天
去签到