IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> MySQL第七讲:数据库事务及MVCC机制 -> 正文阅读

[大数据]MySQL第七讲:数据库事务及MVCC机制

本文是MySQL第七讲:介绍数据库事务及MVCC机制,自己的思考,对技术理解的深度和广度

1、数据库事务(事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态) 20200930补

1、什么是事务?(引擎层)

  • 事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中,事务支持是在引擎层实现的,MySQL原生的MyISAM引擎就不支持事务,这也是MyISAM被InnoDB取代的重要原因之一

举例如下:转账操作。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。

1、MySQL事务特性(数据库事务transanction正确执行的四个基本要素)ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)

  • 1、原子性:整个事务中的所有操作,要么全部完成,要么全部不完成
  • 2、一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏,在innodb引擎中由redo log保证
  • 3、隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作 由MCVV实现
  • 4、持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚

2、事务并发带来的问题

  • 脏读:一个事务读取了另一个事务未提交的数据;
  • 不可重复读:不可重复读的重点是修改,同样条件下两次读取结果不同,也就是说,被读取的数据可以被其它事务修改;
  • 幻读:幻读的重点在于新增或者删除,同样条件下两次读出来的记录数不一样

不可重复度和幻读区别:

  • 不可重复读的重点是修改,幻读的重点在于新增或者删除。

  • 例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

  • 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

3、事务隔离级别是什么?有什么作用? 美团

  • 什么是隔离级别

    • 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题
  • 作用:如果没有采取必要的隔离级别,会导致各种并发问题

    • 脏读:读了没有被提交的数据,
    • 不可重复读:读取的数据某些字段被更新了,
    • 幻读:读取数据的表被增加/减少了数据
  • 隔离级别的种类(以Innodb为例:基于MVCC和锁)

    • 读未提交(READ UNCOMMITTED):就是事务可以读取其它事务未提交的数据。
      • //脏读,不可重复读,幻读都会出现
    • 读已提交(READ COMMITTED):允许事务读取已经被其他事务提交的变更数据
      • //sqlserver oracle默认 我司使用该级别 可避免脏读,但不可重复读,幻读都会出现
    • 可重复读(REPEATABLE READ):(mysql默认)** 保证同一个事务中的多次相同的查询的结果是一致的,在事务期间,禁止其他事务对这个字段进行更新**
      • //可避免脏读和不可重复读,但幻读仍然存在 (mysql的间隙锁已解决了幻读的问题
      • 解决方法:读取数据加行锁
      • 使用场景:银行账单的数据校对
    • 串行化(SERIALIZABLE):在事务期间,禁止其他事务对这个字段进行插、删、改操作
      • //所有并发问题都可以解决
      • 方法:事务串行化顺序执行,读用共享读锁,写用排他写锁,读锁和写锁互斥,若果使用where语句,还会获取间隙锁
隔离级别脏读不可重复读幻影读
READ-UNCOMMITTED
READ-COMMITTED×
REPEATABLE-READ××
SERIALIZABLE×××
  • MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation; 命令来查看,MySQL 8.0 该命令改为SELECT @@transaction_isolation;

  • 与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读),但是可以通过应用加锁读(例如 select * from table for update 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 SERIALIZABLE(可串行化) 隔离级别。

  • 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容)😗*,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失。

  • InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。

4、实战演示脏读/不可重复读/幻读

使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。

MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:START TARNSACTION。

我们可以通过下面的命令来设置隔离级别。

SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]

我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:

  • START TARNSACTION |BEGIN:显式地开启一个事务。
  • COMMIT:提交事务,使得对数据库做的所有修改成为永久性。
  • ROLLBACK:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

脏读(读未提交隔离级别)
在这里插入图片描述

避免脏读(读已提交隔离级别)
在这里插入图片描述

不可重复读

还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题
在这里插入图片描述

可重复读
在这里插入图片描述

防止幻读(可重复读)
在这里插入图片描述
一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?

幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除
参考资料:《MySQL技术内幕:InnoDB存储引擎》

5、InnoDB实现事务原理 美团

事务的ACID是通过InnoDB日志和锁来保证。

  • 事务的隔离性是通过数据库锁的机制实现的,
  • 持久性通过redo log(重做日志)来实现,
  • 原子性和一致性通过Undo log来实现。

undo log 具体怎么回滚事务?

  • UndoLog 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
  • 和Undo Log相反,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。

举例子

  • 对于 insert 类型的 sql, 会在 undo log 中记录下刚才你 insert 进来的数据的 ID, 当你想 roll back 时, 根据 ID 完成精准的删除。
  • 对于 delete 类型的 sql, 会在 undo log 中记录刚才你删除的数据, 当你回滚时会将删除前的数据 insert 进去。
  • 对于 update 类型的 sql, 会在 undo log 中记录下修改前的数据, 回滚时只需要反向 update 即可。

2、隔离级别的实现原理MVCC:以“可重复读”为例

什么是MVCC?

  • 全称是Multi-Version Concurrency Control,即多版本并发控制,MVCC是一种并发控制的方法,它是通过读取历史版本的数据, 来降低并发事务冲突, 从而提高并发性能的一种机制
  • 在编程语言中的实现是使用乐观锁。

MVCC 在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读

当前读

  • 像select lock in share model(共享锁)select for update; update; insert; delete(排它锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

快照读(提高数据库的并发查询能力)

  • 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销,既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

当前读、快照读和MVCC的关系

  • MVCC多版本并发控制指的是维持一个数据的多个版本,使得读写操作没有冲突,快照读是MySQL为实现MVCC的一个非阻塞读功能。MVCC模块在MySQL中的具体实现是有三个隐式字段,undo日志,read view三个组件来实现的

应用场景:

  • 1、在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值
        回滚日志                        最新值
  readviewA    readviewB             readviewC        
  将2改为1<-- 将3改为2 <--将4改为3<--当前值4     (同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC))
  对于read-viewA,要得到1,就必须将当前值依次执行图中所有的回滚操作得到
  • 2、什么时候删除?
    在不需要的时候才删除,就是当系统里没有比这个回滚日志更早的read-view的时候
    注意:
    • 1、尽量不要使用长事务,长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。
    • 2、在MySQL5.5及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有20GB,而回滚段有200GB的库。最终只好为了清理回滚段,重建整个库。

3、事务的启动方式

  • 1、显示启动事务语句,begin或start transaction,配套的提交语句时commit,回滚语句时rollback;
  • 2、set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行commit或rollback语句,或者断开连接。

注意:可以在information_schema库的innodb_trx这个表中查询长事务,比如下面这个语句,用于查找持续时间超过60s的事务

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60;

4、系统里面应该避免长事务,如果你是业务开发负责人同时也是数据库负责人,你会有什么方案来避免出现或者处理这种情况呢?

在开发过程中,尽可能的减小事务范围,少用长事务,如果无法避免,保证逻辑日志空间足够用,并且支持动态日志空间增长。监控Innodb_trx表,发现长事务报警。


5、我们知道:innodb与myisam的区别之一就是对事务的支持方面,innodb通过MVCC机制支持事务的回滚操作,那么能让myisam也支持事务吗?

事务的四个特点:ACID里面, 原子性和持久性做不到,隔离性只能实现基本用不上的串行化(在异常崩溃时也无法保证) 这样的事务不要也罢


6、分布式事务解密?

6.1、什么是分布式事务?

  • 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

6.2、分布式事务产生的原因?

分布式事务产生的原因?

  • 从本地事务来看,我们可以看为两块,一个是service产生多个节点,另一个是resource产生多个节点

    • service多个节点
      • 在我们团队,商品的数据分为好多个部分,比如商品详情,商品属性,商品协议数据等等。在公司内部商品详情,商品属性功能由一个微服务团队维护,商品协议数据又是另外的团队维护。
        在这里插入图片描述
    • resource多个节点(由drds保证)
      • MySQL一般来说装千万级的数据就得进行分库分表,对于一个支付宝的转账业务来说,你给的朋友转钱,有可能你的数据库是在北京,而你的朋友的钱是存在上海,所以我们依然无法保证他们能同时成功。
        在这里插入图片描述

6.3、MySQL事务特性

事务:

  • 事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
  • 数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)
    • 1、原子性:整个事务中的所有操作,要么全部完成,要么全部不完成;
    • 2、一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏;
    • 3、隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作;
    • 4、持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

6.4、分布式事务在电商系统中的应用场景

  • 1、支付:对买家进行扣款,同时对卖家进行加钱,卖家和买家不在同一个数据库中;
  • 2、下单:下单时会发生扣库存和更新订单状态两个动作,分属于不同数据库;
  • 3、店铺创建:店铺创建之后需要初始化仓库,店铺表和仓库表在不同的库;
  • 4、商品审核:商品表和审核详情表分属不同的库。

6.5、分布式事务解决方案

  • 1、2PC 两阶段提交
    在这里插入图片描述

  • 2、消息事务 + 最终一致性(RocketMQ) 柔性事务

    • 消息事务是基于消息中间件的两阶段提交,将本地事务和发消息放在一个分布式事务里,要么本地操作成功并发消息,要么都失败。
    • 实现原理:
      • 1、A系统向消息中间件发送一条预备消息 //出错则事务失败
      • 2、消息中间件保存预备消息并返回成功 //出错则事务失败
      • 3、A执行本地事务 //出错会回滚预备消息,怎么回滚,A系统实现了一个消息中间件的回调接口,MQ会不断执行回调接口
      • 4、A发送提交消息给消息中间件 //出错不需要回滚,MQ会自己对消息进行提交,从而完成整个消息事务
        • 本地消息表
          在这里插入图片描述
          好处:基于MQ的两阶段提价用于高并发的场景,分布式事务被拆解为一个消息事务(A系统的本地操作+发消息),一个B系统的本地操作,消息一定会发送出来,如果B没收到,消息会一直重传,也可以设置重传次数,提高MQ可用性。
    • Demo 待补充
  • 3、TCC编程模式(两阶段提交的变形)

    • 将业务逻辑分为三块:
      • try 预留业务资源(类似DML锁定资源),
      • confirm 确认执行业务操作(类似commit),
      • cancel 取消执行业务操作(类似rollback)
        • 以下单为例:try阶段会扣除库存,confirm阶段更新订单状态,如果更新订单失败,会进入cancel阶段,恢复库存
        • TCC开源框架:tcc-transaction (补偿性分布式事务框架)
    • TCC流程如下图
      在这里插入图片描述

7、MySql集群/分布式事务实现(我的集群是使用读写分离,写MySql一台,读MySql多台)

1、MySql集群本质上是一个存储方案,其具有不共享(不共享的对等节点可以令集群中任意一台服务器上的更新操作,立即在其他服务器上可见)、分布式结点架构的特点,可以提供较好的容错性,并提高性能。

  • 在数据更新的过程中,使用 “读已提交隔离级别” 保证所有结点的数据一致性,使用 “两阶段提交机制” 保证所有结点都有相同的数据(如果任何一个写操作事变,则更新失败)

2、数据存储 (分布式事务)

  • Mysql Cluster采用同步复制实现数据节点组内的主从同步,以此来保证组内节点数据的一致性。

  • 同步过程主要通过 两阶段提交机制 实现:

    • 第一阶段:准备提交

      • 1)执行提交语句时,事务将会被Master(主)发送给所有的Slave(从),每个Slave开始准备提交事务。
      • 2)Slave准备事务完毕后向Master发送OK或ABORT消息,告知Master事务的准备情况。
    • 第二阶段:通过收到的消息判断提交或中止

      • 3 ) Master 接收所有 Slave 发送的 OK 或 ABORT 消息
        • Master 如果收到所有 Slave 的 OK 消息,就会向所有 Slave 发送提交消息,执行提交事务
        • Master 如果收到来自任何一个Slave的ABORT消息,就会向所有 Slave 发送 ABORT消息,取消提交事务。
      • 4 ) Slave接收Master发送的提交或取消请求
        • Slave 收到提交请求,执行提交事务,并向Master发送事务已提交的确认;
        • Slave 收到取消请求,则会撤销所有改变并释放所占用的资源,中止事务,并向Master发送事务已中止的确认
      • 5 ) Master 收到所有 Slave 的确认后,则将报告该事务被提交(或中止),然后继续处理下一个事务
  • 总结:同步复制共需 4 次消息传递,所以 Mysql Cluster 在数据更新的速度上比单机Mysql 要慢


8、选择合适的分布式主键方案?

后续补充

9、JDBC 对事务的支持

对于JDBC而言,每条单独的语句都是一个事务,即每个语句后都隐含一个commit(Connection 提供了一个auto-commit的属性来指定事务何时结束)

try{  
     conn.setAutoCommit(false);  //将自动提交设置为false        
     ps.executeUpdate("修改SQL"); //执行修改操作  
     ps.executeQuery("查询SQL");  //执行查询操作                 
     conn.commit();      //当两个操作成功后手动提交     
}catch(Exception e) {  
     conn.rollback();    //一旦其中一个操作出错都将回滚,使两个操作都不成功  
     e.printStackTrace();  
} //为了能够将多条SQL当成一个事务执行,必须首先通过Connection关闭auto-commit模式,然后通过Connection的setTransactionIsolation()方法设置事务的隔离级别,
//最后分别通过Connection的commit()方法和rollback()方法来提交事务和回滚事务

10、CAP、base理论及其应用

1、CAP定理,又被叫作布鲁尔定理。对于设计分布式系统来说(不仅仅是分布式事务)的架构师来说,CAP就是你的入门理论.

  • CAP理论中C是consistency强一致性,对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
  • A是availablity可用性,非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
  • P是partition tolerance分区容错性 当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
  • CAP理论就是说在分布式存储系统中,最多只能实现上面的两点,我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。(AP是大多数网站架构的选择)(分区容错性必须得实现
  • CP:redis、mongoDB、zookeeper
  • AP:各类分布式系统

2、数据库的写实时性和读实时性需求(数据库事务一致性需求)

  • 对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的

3、对复杂的SQL查询,特别是多表关联查询的需求?

  • 任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。

4、BASE是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案
指标:基本可用(Basically Available)软状态(Soft state)最终一致(Eventually consistent)

  • 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
  • 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
  • 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。

它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标。

5、分布式和集群

  • 分布式:不同服务器部署不同的工程,他们之间通过rpc/rmi通信和调用,对外提供服务
  • 集群:不同服务器部署相同的服务模块,通过分布式调度软件进行统一的调度

11、raft算法

todo

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-06-21 21:28:50  更:2022-06-21 21:29:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/29 7:25:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码