【MySQL】第3期 索引、事务与视图系统详解

目录

  • 开头
  • 一.索引
    • [1.底层基础:磁盘 IO 与 Page 机制](#1.底层基础:磁盘 IO 与 Page 机制)
      • [(1)buffer pool 缓冲池](#(1)buffer pool 缓冲池)
      • [(2)Page 结构](#(2)Page 结构)
        • [a. 单个 Page 的内部结构](#a. 单个 Page 的内部结构)
        • [b.多 Page 组织:从线性链表到 B + 树](#b.多 Page 组织:从线性链表到 B + 树)
        • [c.B + 树](#c.B + 树)
        • [B + 树的核心优势](#B + 树的核心优势)
      • [(3)聚簇索引 / 非聚簇索引](#(3)聚簇索引 / 非聚簇索引)
        • [a.MyISAM 引擎:全非聚簇索引](#a.MyISAM 引擎:全非聚簇索引)
        • [b. InnoDB 引擎:聚簇主键 + 非聚簇辅助索引](#b. InnoDB 引擎:聚簇主键 + 非聚簇辅助索引)
    • 2.索引操作
  • [二. 事务](#二. 事务)
  • 三.视图
  • 结尾
  • 往期回顾

开头

Ok了,前面两期我们重点学习到了DDL、DML,今天这一期要来学习MySQL中的两个十分重要的板块------索引+事务,废话不多说,我们字节开始

一.索引

索引是典型的「空间换时间」优化方案,通过建立类似 "目录" 的数据结构,大幅减少磁盘 IO 次数,实现海量数据下的秒级查询。索引提升查询速度的代价,是写入(增删改)时需要额外维护索引结构,因此索引的价值在于优化高频查询场景

1.底层基础:磁盘 IO 与 Page 机制

磁盘是机械存储设备,依靠磁头移动寻址,随机 IO 的效率远低于连续 IO,数据库性能的核心瓶颈通常就是磁盘 IO 的次数

  • 磁盘硬件的最小读写单位是扇区,传统大小为 512 字节
  • 操作系统文件系统以4KB 数据块为单位和磁盘交互
  • MySQL InnoDB 引擎和磁盘交互的基本单位是16KB 的 Page(数据页)

可通过命令验证 InnoDB 页大小:

复制代码
SHOW GLOBAL STATUS LIKE 'innodb_page_size';
-- 返回值:16384 字节 = 16KB

(1)buffer pool 缓冲池

MySQL 在内存中开辟了一块大内存空间叫Buffer Pool,作为磁盘数据的缓存:

  • 读写数据优先操作内存中的 Page,后续按策略刷新到磁盘
  • 一次 IO 将整页 16KB 加载进内存,后续同页内的查询无需再访问磁盘
  • 核心优化逻辑:减少 IO 次数,比提升单次 IO 数据量更重要

(2)Page 结构

a. 单个 Page 的内部结构

首先有一个问题:为什么MySQL与磁盘进行IO时,要以Page作为基本单位,为什么不用多少加载多少呢??

如果MySQL要查找id=2的记录,第一次加载id=1,第二次加载id=2,一次一条记录,那么就需要2次IO。如果要找id=5,那么就需要5次IO。

但,如果这5条(或者更多)都被保存在一个Page中(16KB,能保存很多记录),那么第一次IO查找id=2的时候,整个Page会被加载到MySQL的Buffer Pool中,这里完成了一次IO。但是往后如果在查找id=1,3,4,5等,完全不需要进行IO了,而是直接在内存中进行了。所以,就在单Page里面,大大减少了IO的次数

MySQL中要管理很多数据文件,目前我们可以将一个个独立的文件是由一个或者多个Page构成的

复制代码
create table if not exists user (
id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引
age int not null,
name varchar(16) not null
);

--插入多条记录,注意,我们并没有按照主键的大小顺序插入哦
mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.00 sec)

如上图,我们向一张表中插入了5条数据,那么在Page结构中是什么样的呢??

一个 Page 不只是存储用户数据的内存块,还包含管理元数据和页内索引,整体分为三部分:

  1. 页头元数据
    最核心的是两个双向链表指针,用于多页之间的组织:
  • page_prev:指向前一个相邻的 Page
  • page_next:指向后一个相邻的 Page
    所有 Page 通过这两个指针,在逻辑上组成双向链表,实现多页的有序串联。
  1. 用户数据区
    存储一行一行的用户记录,有两个核心规则:
  • 按主键升序排序:所有记录严格按照主键值从小到大排列,这是后续所有索引优化的基础。
  • 记录间用链表串联:每条记录都有指针指向下一条记录,形成单向链表结构
  1. 页目录

页目录就是页内的 "小型索引",专门解决页内线性遍历效率低的问题。

  • 存储内容:多个目录项,每个目录项 = 主键值 + 对应记录在页内的位置指针。
  • 工作方式:目录项本身按主键有序排列,查找时先对页目录做二分查找,快速定位到记录所在的大致区间,再在小区间内遍历即可

b.多 Page 组织:从线性链表到 B + 树

当数据量超过单个 Page 的 16KB 容量时,MySQL 会开辟新的 Page 存储数据,所有 Page 通过page_prev/page_next组成双向有序链表,Page 之间按主键范围有序(前一页的最大主键 < 后一页的最小主键)

多页链表的性能问题

果只有线性链表结构,查找某条记录需要:

从第一个 Page 开始,逐个把 Page 加载到内存,遍历每个 Page 的页目录,直到找到目标记录。

本质是跨 Page 的线性查找,数据量越大,IO 次数越多。千万级数据可能需要上千次 IO,效率极低

解决方案:给 Page 加 "多层目录"

借鉴 "页目录加速页内查找" 的思路,给 Page 也加目录,形成多层级的目录结构:

  • 目录页:不存储用户数据,只存储「下级 Page 的最小主键值 + 下级 Page 的物理指针」。
  • 目录页本身也是 Page,大小同样是 16KB;因为只存键值 + 指针,单个目录页可以容纳几百上千个索引项

可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担

心,可以在加目录页

通过多层目录的叠加,最终形成B + 树结构,这是 InnoDB 索引的底层实现


c.B + 树
B + 树的核心优势
  • 树高极低,IO 次数少:非叶子节点只存键值,单个节点能容纳大量索引项。千万级数据的 B + 树高度仅 3~4 层,意味着一次查询最多只需要 3~4 次磁盘 IO,相比线性遍历性能提升上百倍。
  • 天然支持范围查询、排序:叶子节点按主键有序且通过链表串联,范围查询只需要找到起始位置,沿链表遍历即可,无需回溯。
  • 查询性能稳定:任何主键查询都要从根走到叶子节点,IO 次数基本一致,性能波动小

模拟 MySQL 主键查询流程(聚簇索引)

以select * from user where id = 3;为例,完整查询流程如下:

  1. 定位根页: MySQL从数据字典中获取该表聚簇索引的根页物理地址
  2. 第一层:加载根目录页: 将根页加载到内存的buffer pool 中;在根页的索引项中二分查找,找到 id=3 的所属的下级目录
  3. 中间层:逐层路由: 加载对应的下级目录页,继续二分查找索引项,定位更下层的页
  4. 叶子层:定位数据叶: 最终定位到对应叶子的Page数据页,加载到内存中
  5. 页内查找: 在Page数据页中的页目录中二分查找,找到id=3的记录,取出返回给客户端

补充:Buffer Pool 缓存机制

加载过的 Page 会缓存在内存的 Buffer Pool 中。如果后续再次查询同一条或同页的数据,直接命中内存,无需磁盘 IO,这也是 MySQL 热点数据查询极快的重要原因

(3)聚簇索引 / 非聚簇索引

根据 "索引和数据是否存储在一起",索引分为聚簇索引和非聚簇索引两大类

  • 聚簇索引:索引结构与用户数据存储在一起,B + 树的叶子节点直接存放完整的用户行数据。数据本身就是索引的一部分。
  • 非聚簇索引:索引结构与用户数据分离,B + 树的叶子节点只存放 "数据的位置标识"(磁盘地址或主键),查完索引需要二次查找才能拿到完整数据
a.MyISAM 引擎:全非聚簇索引

MyISAM 的所有索引都是非聚簇索引,索引和数据完全分离

  • 文件结构:

    • .MYI 文件:存储索引B+树
    • .MYD 文件:存储真实用户数据
  • 主键索引 :叶子节点存储对应记录的磁盘物理地址。

  • 辅助索引(普通索引):结构和主键索引完全一致,叶子节点同样存储数据的磁盘地址。

  • 查询流程:查索引 B + 树 → 得到磁盘地址 → 直接去磁盘对应位置读取数据。

  • 特点:辅助索引无需回表到主键索引,直接通过地址读数据;但数据和索引分离,随机 IO 较多


b. InnoDB 引擎:聚簇主键 + 非聚簇辅助索引

InnoDB 是 MySQL 默认引擎,采用「一个聚簇索引 + 多个非聚簇辅助索引」的架构

(1)主键索引(聚簇索引)

  • 叶子节点直接存储完整的用户行数据,整张表的数据就存放在主键 B + 树的叶子节点上。
  • 一张表有且只有一个聚簇索引,优先级规则:
    1. 表有主键,主键就是聚簇索引
    2. 无主键,选第一个唯一非空索引作为聚簇索引
    3. 都没有,自动生成 6 字节的隐藏主键DB_ROW_ID作为聚簇索引
  • 优点:主键查询性能极高,一次 B + 树遍历就能拿到完整数据;范围查询效率高。
  • 缺点:辅助索引查询需要回表;无序主键插入会导致页分裂,产生数据碎片

(2)辅助索引(非聚簇索引)

普通索引、唯一索引、前缀索引等都属于辅助索引,全部是非聚簇的

  • 叶子节点不存储完整数据,只存储主键值。
  • 回表查询 :通过辅助索引查询数据时,需要分两步:
    1. 遍历辅助索引 B + 树,找到对应的叶子节点,获取主键值
    2. 拿着主键值,去聚簇索引 B + 树中再查一次,拿到完整行数据
      这个二次查询的过程就叫做回表

为什么辅助索引存主键,不存磁盘地址?

因为 InnoDB 是聚簇组织,数据页会因为插入、分裂发生位置移动,地址不稳定;而主键是永久不变的,存主键虽然多一次回表,但维护成本更低,数据一致性更好

2.索引操作

索引的四大类型:

  1. 主键索引(primary key)
  • 特性:非空、唯一,一张表只能有一个,查询效率最高
  • 适用:数据行的唯一标识,通常用自增 ID
  1. 唯一索引 (unique)
  • 特性:值必须唯一,但允许为 NULL(多个 NULL 不算重复);一张表可以有多个
  • 适用:业务上需要去重的字段,如手机号、邮箱、身份证号
  1. 普通索引(index)
  • 特性:允许重复值,一张表可以有多个,开发中最常用
  • 适用:频繁作为查询条件、但值不唯一的字段\
  1. 全文索引(fulltext)
  • 特性:针对大文本字段的关键词检索
  • 限制:默认仅 MyISAM 引擎支持,原生对中文支持差;中文全文检索通常使用 Elasticsearch 等专门引擎
a. 创建索引

b.查看索引

c.删除索引

二. 事务

为什么需要事务?------ 并发场景下的数据安全问题

在多客户端同时操作数据库的场景下,如果不对数据操作做任何控制,必然会出现数据一致性问题。最经典的就是火车票超卖问题:

要解决这类问题,数据操作必须满足四个核心要求:

  1. 一次买票的所有操作是一个整体,不能执行一半(原子性)
  2. 两个客户端的买票操作不能互相干扰(隔离性)
  3. 买票成功后数据要永久生效,不能因为崩溃丢失(持久性)
  4. 操作前后数据的状态始终是合法、一致的(一致性)

事务(Transaction)是一组逻辑上相互关联的 DML(增删改)语句集合,是数据库执行的最小逻辑单元 。

这一组语句对外表现为一个不可分割的整体:要么全部执行成功,要么全部执行失败并回滚到事务开始前的状态,不会出现执行了一半的中间状态。

在 MySQL 中,只有 InnoDB 存储引擎支持事务

1.事务的四大核心特性(ACID)

事务必须同时满足四大特性,简称 ACID:

  1. 原子性(Atomicity)

事务是最小的不可分割单元,事务内的所有操作要么全部完成,要么全部不完成,不会停在中间某个环节。

  • 事务执行中如果发生错误 / 异常,会自动回滚(Rollback)到事务开始前的状态,就像这个事务从来没执行过一样
  • 实现基础:undo log(回滚日志)

  1. 一致性(Consistency)

事务执行前后,数据库始终处于数据完整性、业务规则都成立的一致状态。

  • 比如转账场景:A 扣钱、B 加钱,事务结束后总金额必须不变
  • 一致性是事务的最终目标,它由原子性、隔离性、持久性共同保障,同时也和业务逻辑强相关

  1. 隔离性(Isolation)
    数据库支持多个事务并发执行,隔离性保证多个事务之间互不干扰,避免并发交叉执行导致的数据不一致。
  • 隔离性分不同等级,等级越高,数据越安全,但并发性能越低
  • 实现基础:锁机制 + MVCC(多版本并发控制)

  1. 持久性(Durability)
    事务一旦提交(commit),对数据的修改就是永久生效的,哪怕后续系统崩溃、断电,数据也不会丢失。

2.事务的提交模式与基础操作

(1)两种提交模式

MySQL 的事务提交分为两种模式,由系统变量autocommit控制:

切换命令:

复制代码
-- 关闭自动提交
SET AUTOCOMMIT = 0;
-- 开启自动提交
SET AUTOCOMMIT = 1;
-- 查看当前状态
show variables like 'autocommit';

(2)手动事务相关语法

复制代码
-- 1. 开启事务(两种写法等价,推荐begin)
begin;
-- 或 start transaction;

-- 2. 执行业务SQL(增删改查)

-- 3. 创建保存点:可以回滚到指定中间位置
savepoint sp1;

-- 4. 回滚到保存点
rollback to sp1;

-- 5. 全部回滚:回到事务开启前的状态
rollback;

-- 6. 提交事务:所有修改永久生效
commit;

如上图,我们创建了一个表结构,下面会以它举例

为了方便演示,我们将隔离级别设置为读未提交

如上图,使用start transaction; 和 begin; 开启事务,因为MySQL的事务此时可以并发去访问数据区,我们现在也是用两台机器模拟一下

可以看到,向account 表中插入数据,另一个事务在 Read Uncommitted 的隔离级别下是可以读到的,做出的修改另外一个事务也是可以同步查看的

如上图,我们想要进行回滚也是Ok的

可是如果我们直接按下 Ctrl+\ 直接退出不保存,发现另一个事务现在什么都读不到了

看完以上的内容,你现在大概是云里雾里的,没关系,这只是我们为了引出隔离性,下面我们来正式学习下

3.事务隔离级别 ------ 隔离性的分级实现

数据库是多客户端并发访问的系统。如果多个事务同时读写同一张表、同一行数据,不加任何限制会出现脏读、不可重复读、幻读等数据异常。隔离级别就是用来定义「事务之间的可见性边界」,平衡数据安全与并发性能

三类并发异常

(1)Read Uncommitted :读未提交

级别定义

最低隔离级别,事务之间几乎没有隔离:所有事务都可以看到其他事务未提交的执行结果。

  • 本质:写操作不加排他锁,读操作不加共享锁,直接读取最新版本
  • 实际生产中几乎不会使用,仅用于理论学习

存在问题

脏读、不可重复读、幻读三类问题全部存在。

上面我们举得那个例子就是读未提交得典型例子


(2)Read Committed :读提交

级别定义

一个事务只能看到其他事务已经提交的修改,未提交的修改完全不可见。

  • 是 Oracle、SQL Server 等大多数数据库的默认隔离级别
  • 解决了脏读问题,但仍存在不可重复读和幻读

实现原理

每次执行SELECT快照读时,都会重新生成一个最新的Read View(读视图),所以每次都能看到最新提交的修改

如上图,我们现在将两个事务都设置成 Read Committed 隔离模式,以及 autocommit =0

如上图,read committed 隔离级别下,如果读没有提交,另外得事务就不会显示

当我们commit 提交后,在另一个事务中select 就可以查询到了,但是现在查得表中得数据并没有修改,这就是不可重复读问题

(3)Repeatable read:可重复读

级别定义

MySQL 默认隔离级别,保证同一个事务内,多次快照读取同一行数据,结果始终一致

  • 解决了脏读、不可重复读问题
  • 通过Next-Key 锁(间隙锁 + 行锁) 基本解决了幻读问题

实现原理

事务中第一次执行 SELECT 快照读时,生成一个 Read View,后续所有快照读都复用这个 Read View,所以看不到之后提交的修改。

现在我们将隔离级别设置为repeatable read 可重复读

如果我们没有 commit 提交,另一个事务就查询不到,后续我们更新或者回滚,都不会影响另一个事务查询结果------可重复读

(4)Serializable:串行化

级别定义

最高隔离级别,强制所有事务串行执行,读写操作全部加锁,事务之间完全不会交叉。

  • 彻底解决脏读、不可重复读、幻读所有问题
  • 并发性能极差,容易出现锁等待、超时、死锁,生产环境几乎不使用

就是单次只能一个事务进行对表的操作

4.多版本并发控制( MVCC )

MVCC(Multi-Version Concurrency Control,多版本并发控制)是 InnoDB 引擎实现事务隔离性的核心无锁机制。它通过为每行数据维护多个历史版本,让读写操作互不阻塞

MVCC 的核心思想

为每行数据维护多个历史版本,读操作和写操作访问不同的版本:

  • 写操作:修改最新版本的数据,同时保留旧版本
  • 读操作:读取事务开始时刻的历史快照,不需要加锁,也不会被写操作阻塞

最终实现:读不阻塞写,写也不阻塞读,从根本上解决了读写冲突问题。

MVCC 的实现依赖三个核心组件:行隐藏字段、undo log 回滚日志、Read View 读视图

(1)3个行隐藏字段

InnoDB 中,每一行用户数据除了我们定义的字段,还会自动添加 3 个隐藏字段,是 MVCC 的基础:

补充:还有一个隐藏的删除标记位。删除数据不是直接物理删除,只是把标记位置为已删除,后续由后台线程清理。这样删除也能形成历史版本,支持回滚

如上图,创建了一个表,插入了一个数据,select 的查询结果只有两列,实际上呢:

(2)undo log日志

undo log(回滚日志)是存储历史数据的地方,MVCC 的多版本本质上就是靠 undo log 实现的

现在我们要将这条信息的名字从张三改成李四,如下图:

那么此时假如是id=10的事务执行这条SQL语句,在 undo log 中是这样的:

现在又有一个事务11,对student表中记录进行修改(update):将age(28)改成age(38)

上面的一个一个版本,我们可以称之为一个一个的快照

(3)read view :读视图

Read View 是事务执行快照读时生成的「数据可见性快照」,用来判断当前事务能看到版本链中的哪一个版本

其实,read view 就是个类对象

复制代码
class ReadView {
// 省略...
private:
	/** 高水位,大于等于这个ID的事务均不可见*/
	trx_id_t m_low_limit_id
	我们在实际读取数据版本链的时候,是能读取到每一个版本对应的事务ID的,即:当前记录的
	DB_TRX_ID 。
	那么,我们现在手里面有的东西就有,当前快照读的 ReadView 和 版本链中的某一个记录的
	DB_TRX_ID 。
	所以现在的问题就是,当前快照读,应不应该读到当前版本记录。一张图,解决所有问题!
	/** 低水位:小于这个ID的事务均可见 */
	trx_id_t m_up_limit_id;
	/** 创建该 Read View 的事务ID*/
	trx_id_t m_creator_trx_id;
	/** 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
	trx_id_t m_low_limit_no;
	/** 标记视图是否被关闭*/
	bool m_closed;
	// 省略...
};

Read View 的四个核心参数


a.核心规则:数据可见性判断

当事务执行快照读时,会从版本链的最新版本 开始,依次拿每条记录的 DB_TRX_ID 和 Read View 对比,判断该版本对当前事务是否可见

判断步骤(按优先级)

  1. 如果 DB_TRX_ID < up_limit_id
    该事务在快照生成之前就已经提交了 → 可见
  2. 如果 DB_TRX_ID >= low_limit_id
    该事务是在快照生成之后才开启的 → 不可见
  3. 中间范围(up_limit_id ≤ DB_TRX_ID < low_limit_id)
    判断 DB_TRX_ID 是否在 m_ids 列表中:
    • 在列表中 :该事务在生成快照时处于活跃未提交状态 → 不可见
    • 不在列表中 :该事务在生成快照前已经提交 → 可见

b.两种读取模式

MySQL 的数据读取分为两类,MVCC 只作用于快照读

  1. 快照读

读取数据的历史版本,不加锁,靠 MVCC 实现。

  • 对应操作:普通的 select 语句
  • 特点:不会阻塞写操作,性能极高
  • 适用场景:绝大多数普通查询场景
  1. 当前读

读取数据的最新版本,需要加锁。

  • 对应操作:
    • insert、update、delete 写操作
    • select ... for update(排他锁)
    • select ... lock in share mode(共享锁)
  • 特点:会阻塞其他写操作,保证读取到的是最新数据
  • 适用场景:需要严格实时性的写前校验场景

c.RC 与 RR 隔离级别的本质区别

读提交(RC)和可重复读(RR)两个隔离级别,核心差异就在于 Read View 的生成时机不同

  1. 读提交(Read Committed,RC)

规则:每次执行快照读,都会生成一个全新的 Read View

每次查询都会获取当前最新的快照,因此能看到其他事务刚刚提交的修改。

  • 优点:数据实时性高
  • 缺点:同一个事务内,两次相同查询可能得到不同结果 → 产生不可重复读问题
  1. 可重复读(Repeatable Read,RR)

规则:事务中第一次执行快照读时生成 Read View,之后所有快照读都复用这同一个视图

整个事务看到的都是同一个时间点的数据快照,无论其他事务是否提交修改,当前事务的查询结果始终一致。

  • 优点:同一个事务内读取结果始终一致 → 实现了可重复读
  • 配合 Next-Key 锁(间隙锁 + 行锁),进一步解决了幻读问题
  • 这是 MySQL 的默认隔离级别

三.视图

视图是一种虚拟表,本身不实际存储数据,其内容完全由一条 SELECT 查询语句定义

视图与底层基表数据实时联动:

  • 基表数据发生变化,视图的查询结果会同步更新
  • 对简单视图执行增删改操作,会直接作用到底层基表

创建视图

语法:

复制代码
CREATE VIEW 视图名 AS SELECT查询语句;

视图的 DML 操作(修改基表)

对于单表、无聚合、无分组的简单视图,支持执行 INSERT、UPDATE、DELETE 操作,修改会直接作用到底层基表。


删除视图

复制代码
DROP VIEW 视图名;

结尾

这一期对于MySQL的学习就到这里了,希望对你的学习有所帮助。感谢你的点赞、关注。我主页里有更好康的呦!

往期回顾

  1. 【MySQL】 第2期 查询与函数系统详解:从基础 CRUD 到复合多表查询
  2. 【MySQL】 第1期 从核心概念到表约束全解析
  3. 【Linux】第16期 讲解数据链路层、ARP、NAPT 与内网穿透
相关推荐
anew___2 小时前
《从零手写操作系统 (49):虚拟内存进阶——Demand Paging、Swap与缺页异常处理》
linux·服务器·php
for_ever_love__2 小时前
增量索引实战:新文档怎么做到分钟级可检索
实时·文档·索引·更新·rag·增量·新增
迷途之人不知返2 小时前
【基础IO】-4-再谈重定向
linux
Android系统攻城狮2 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_parse_caps调用流程与实战(八十)
linux·运维·服务器·gstreamer音视频·音视频进阶
erzong2313 小时前
Linux网络:UDP编程踩坑日记
linux·网络·udp
盐焗鹌鹑蛋3 小时前
【Linux】线程库与封装
linux
lwx9148523 小时前
Deepin操作系统
linux·服务器·网络
先知后行。3 小时前
进程间通信(IPC)
linux·运维·服务器
misty youth3 小时前
Navicat与PHPstudy小皮
mysql·adb·php·navicat