当前位置: 首页 > article >正文

pipe匿名管道实操(Linux)

管道相关函数

1 pipe

  • 是 Unix/Linux 系统中的一个系统调用,用于创建一个匿名管道
#include <unistd.h>
int pipe(int pipefd[2]);
参数说明:
pipefd[2]:一个包含两个整数的数组,用于存储管道的文件描述符:
pipefd[0]:管道的读端(用于从管道读取数据)巧记:用嘴巴口型(o)读
pipefd[1]:管道的写端(用于向管道写入数据)巧记:用笔(1)写
返回值:
成功时返回 0
失败时返回 -1 并设置 errno

2 error

errno 是 C 和 C++ 中用于报告错误的全局变量(或宏),全称为 "error number"。它由系统或标准库函数在操作失败时设置,用于指示具体的错误原因。代码出错时我们更想知道出错原因,就可以用error

常见 errno 错误码

错误码宏含义
EPERM1操作无权限
ENOENT2文件或目录不存在
EINTR4系统调用被中断
EIO5输入/输出错误
EBADF9错误的文件描述符
EAGAIN11资源暂时不可用
ENOMEM12内存不足
EACCES13权限不足
EFAULT14非法内存访问
EEXIST17文件已存在
EDOM33数学参数超出定义域
ERANGE34结果超出范围

一般和和strerror配合一起使用 

#include <iostream>
#include <cerrno>  
#include <cstring>int main() {errno = 0; // 先重置 errnodouble x = sqrt(-1.0); // 尝试计算负数的平方根if (errno == EDOM) {   // EDOM 是域错误宏std::cerr << "Error: " << std::strerror(errno) << "\n";}
}
输出:
Error: Numerical argument out of domain

3 strerror 

  •  是 C 标准库中的一个函数,用于将错误代码(errno 值)转换为可读的错误描述字符串。下面我会详细解释它的用法和实际应用场景。
#include <string.h>  
char *strerror(int errnum);
参数说明:
errnum:错误编号(通常是 errno 的值)
返回值:
返回指向错误描述字符串的指针(静态分配的字符串,不可修改)
不会失败(永远返回有效指针)

4 推荐使用 #include <cerrno> 而不是 #include <errno.h>


1. 符合 C++ 标准库的命名规范

C++ 标准库对 C 标准库的头文件进行了重新封装,采用无 .h 后缀的形式(如 <cstdio><cstdlib><cerrno>),以区别于 C 的传统头文件(如 <stdio.h>stdlib.herrno.h>)。

  • <cerrno> 是 C++ 标准化的头文件,明确属于 C++ 标准库。
  • <errno.h> 是 C 风格的头文件,虽然 C++ 兼容它,但不推荐在新代码中使用。

2. 潜在的命名空间管理

理论上,<cerrno> 将相关名称(如 errnoEDOMERANGE)放入 std 命名空间,而 <errno.h> 直接将它们暴露在全局命名空间。虽然实际实现中(由于兼容性要求):

  • errno 仍然是全局宏(无法放入 std)。
  • EDOMERANGE 等宏通常在全局命名空间也可用。

但使用 <cerrno> 能更清晰地表达“这是 C++ 代码”的意图,并可能在未来的标准中更好地支持命名空间隔离。


注意事项

  • errno 仍是全局宏:即使使用 <cerrno>errno 也不会变成 std::errno(因为它是宏)。
  • 错误码宏(如 EDOM:大多数实现仍允许全局访问,但理论上可以额外通过 std::EDOM 访问(尽管实践中很少需要)。

fork() 系统调用详解

  • fork() 是 Unix/Linux 系统中的一个重要系统调用,用于创建一个新的进程(子进程)
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回值:
父进程:返回子进程的 PID(进程ID,> 0)。
子进程:返回 0。
出错时:返回 -1(并设置 errno)。

6 exit

  • 是一个标准库函数,用于终止当前进程,并返回一个状态码给操作系统。它是进程正常退出的标准方式
#include <stdlib.h>
void exit(int status);
参数:
status:进程的退出状态码:
0 或 EXIT_SUCCESS:表示成功退出。
非零值(通常 EXIT_FAILURE=1):表示失败退出(具体含义由程序定义)

exit() 的运行机制

(1) 进程终止流程

当调用 exit() 时,操作系统会按顺序执行以下操作:

  1. 调用 atexit() 注册的函数(按注册的逆序执行)。

  2. 刷新所有标准 I/O 缓冲区(如 printf 未输出的内容会被强制写入)。

  3. 关闭所有打开的文件描述符

  4. 释放进程占用的内存和其他资源

  5. 向父进程发送状态码(可通过 wait() 或 $? 获取)。

(2) exit() vs _exit()

函数说明
exit()标准 C 库函数,会执行清理(刷新缓冲区、调用 atexit() 等)。
_exit()系统调用(<unistd.h>),直接终止进程,不执行任何清理

 7 snprintf 

snprintf 是 C 标准库中的一个格式化输出函数,用于安全地格式化字符串并写入缓冲区,比传统的 sprintf 更安全,因为它可以防止缓冲区溢出(Buffer Overflow)

#include <stdio.h>
int snprintf(char *str,       // 目标缓冲区size_t size,     // 缓冲区大小(最多写入 size-1 个字符 + '\0')const char *format,  // 格式化字符串(类似 printf)...              // 可变参数(要格式化的数据)
);
返回值:
成功:返回理论写入的字符数(不包括结尾的 \0),即使缓冲区不够。
错误:返回负值(如编码错误)。

    getpid()  getppid()

    • getpid() 是 Unix/Linux 系统编程中的一个基础系统调用,用于获取当前进程的进程ID(PID)
    • getppid() 是 Unix/Linux 系统调用,用于获取当前进程的父进程 PID(Process ID)
    #include <unistd.h>  // 必须包含的头文件
    pid_t getpid(void);  // 返回当前进程的 PID
    返回值:
    成功:返回当前进程的 PID(正整数)
    不会失败(无错误码)#include <unistd.h>  // 必须包含的头文件
    pid_t getppid(void); // 返回父进程的 PID
    返回值:
    成功:返回父进程的 PID(正整数)
    不会失败(无错误码)

    sizeof 

    sizeof 是 C/C++ 中的一个编译时运算符(不是函数!),用于计算变量、类型或表达式所占的内存大小(字节数)。它是静态计算的,不会在运行时影响程序性能

    sizeof(变量或类型)
    返回值:
    size_t 类型的无符号整数(通常是 unsigned int 或 unsigned long)。
    计算时机:在编译时确定,不会执行括号内的代码(如果传入表达式)

    语法规则

    操作对象示例是否必须加括号备注
    变量名sizeof a可选更简洁,但可能降低可读性
    类型名sizeof(int)必须不加括号会导致编译错误
    表达式sizeof(a + b)必须表达式需用括号包裹

    示例 

    int arr[10];变量(括号可选)
    size_t s1 = sizeof arr;     // 计算数组总大小
    size_t s2 = sizeof(arr);    // 等效写法类型(括号必须)
    size_t s3 = sizeof(int);    // 计算 int 类型大小表达式(括号必须)
    size_t s4 = sizeof(arr[0]); // 计算数组元素大小结构体/类成员的大小
    struct S { int x; double y; };
    size_t s = sizeof(S::x);  // C++ 中合法,计算成员大小

    创建管道实操

    makefile

    mypipe:mypipe.ccg++ -o $@ $^ -std=c++11
    .PHONY:clean
    clean:rm -rf mypipe

    mypipe.cc

    #include <iostream>
    #include <string>
    #include <cerrno>
    #include <cassert>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/wait.h>int main()
    {int pipefd[2] = {0};int n = pipe(pipefd);if(n < 0){std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;return 1;}pid_t id = fork();assert(id != -1); if(id == 0){close(pipefd[0]);int cnt = 0;while(true){char x = 'X';write(pipefd[1], &x, 1);std::cout << "Cnt: " << cnt++<<std::endl;sleep(1);}close(pipefd[1]);exit(0);}close(pipefd[1]);char buffer[1024];int cnt = 0;while(true){int n = read(pipefd[0], buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = '\0';std::cout << "我是父进程, child give me message: " << buffer << std::endl;}else if(n == 0){std::cout << "我是父进程, 读到了文件结尾" << std::endl;break;}else {std::cout << "我是父进程, 读异常了" << std::endl;break;}sleep(1);if(cnt++ > 5) break;}close(pipefd[0]);int status = 0;waitpid(id, &status, 0);std::cout << "sig: " << (status & 0x7F) << std::endl;sleep(100);return 0;
    }

     

    • mypipe:目标文件(可执行文件)名称
    • mypipe.cc:依赖文件(源代码文件)
    • g++ -o $@ $^ -std=c++11:编译命令
      • $@ 表示目标文件(mypipe)
      • $^ 表示所有依赖文件(这里只有 mypipe.cc)
      • -std=c++11 指定使用 C++11 标准
    • .PHONY:clean:声明 clean 是一个伪目标(不是实际文件)
    • rm -rf mypipe:删除生成的可执行文件

    父进程管理多个子进程实现管道通信实操

    Makefile

    ctrlProcess:ctrlProcess.ccg++ -o $@ $^ -std=c++11
    .PHONY:clean
    clean:rm -rf ctrlProcess
    

    Task.hpp

    #pragma once#include <iostream>
    #include <vector>
    #include <unistd.h>typedef void (*fun_t)(); void a() { std::cout << "a任务正在执行...\n" << std::endl; }
    void b() { std::cout << "b任务正在执行...\n" << std::endl; }
    void c() { std::cout << "c任务正在执行...\n" << std::endl; }#define A 0
    #define B 1
    #define C 2class Task
    {
    public:Task(){funcs.push_back(a);funcs.push_back(b);funcs.push_back(c);}void Execute(int command){if (command >= 0 && command < funcs.size()) funcs[command]();}public:std::vector<fun_t> funcs;
    };

    ctrlProcess.cc

    #include <iostream>
    #include <string>
    #include <vector>
    #include <cassert>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <sys/types.h>
    #include "Task.hpp"
    using namespace std;const int gnum = 3;
    Task t;class EndPoint
    {
    private:static int number;
    public:pid_t _c_id;int _w_fd;string processname;
    public:EndPoint(int id, int fd) :_c_id(id), _w_fd(fd){//process-0[pid:fd]char namebuffer[64];snprintf(namebuffer, sizeof(namebuffer), "process-%d[%d:%d]", number++, _c_id, _w_fd);processname = namebuffer;}string name() const { return processname; }
    };int EndPoint::number = 0;void WaitCommand()
    {while(1){int command = 0;int n = read(0, &command, sizeof(command));if (n == sizeof(int)) t.Execute(command);else if (n == 0){std::cout << "父进程关闭了写端" << getpid() << std::endl;break;}else break;}
    }void createProcesses(vector<EndPoint> *end_points)
    {vector<int> fds;for(int i = 0; i < gnum; ++i){int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0); (void)n;pid_t id = fork();assert(id != -1);if (id == 0){for(auto &fd : fds) close(fd);close(pipefd[1]);dup2(pipefd[0], 0);WaitCommand();close(pipefd[0]);exit(0);}close(pipefd[0]);end_points->push_back(EndPoint(id, pipefd[1]));fds.push_back(pipefd[1]);}
    }int ShowBoard()
    {std::cout << "##########################################" << std::endl;std::cout << "|   0. 执行日志任务   1. 执行数据库任务    |" << std::endl;std::cout << "|   2. 执行请求任务   3. 退出             |" << std::endl;std::cout << "##########################################" << std::endl;std::cout << "请选择# ";int command = 0;std::cin >> command;return command;
    }void ctrlProcess(const vector<EndPoint> &end_points)
    {int cnt = 0;while(true){int command = ShowBoard();if (command == 3) break;if (command < 0 || command > 2) continue;int index = cnt++;cnt %= end_points.size();string name = end_points[index].name();cout << "选择了进程: " <<  name << " | 处理任务: " << command << endl;write(end_points[index]._w_fd, &command, sizeof(command));sleep(1);}
    }void waitProcess(const vector<EndPoint> &end_points)
    {for(int i = 0; i < end_points.size(); ++i){std::cout << "父进程让子进程退出:" << end_points[i]._c_id << std::endl;close(end_points[i]._w_fd);waitpid(end_points[i]._c_id, nullptr, 0);std::cout << "父进程回收了子进程:" << end_points[i]._c_id << std::endl;}
    }// #define A 0
    // #define B 1
    // #define C 2int main()
    {vector<EndPoint> end_points;createProcesses(&end_points);ctrlProcess(end_points);waitProcess(end_points);return 0;
    }

    相关文章:

    pipe匿名管道实操(Linux)

    管道相关函数 1 pipe 是 Unix/Linux 系统中的一个系统调用&#xff0c;用于创建一个匿名管道 #include <unistd.h> int pipe(int pipefd[2]); 参数说明&#xff1a; pipefd[2]&#xff1a;一个包含两个整数的数组&#xff0c;用于存储管道的文件描述符&#xff1a; pi…...

    2025.04.10-拼多多春招笔试第三题

    📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 数字重排最大化问题 问题描述 LYA是一位专业的数字设计师。她手中有两个数字序列 s 1 s_1...

    Vue.js组件安全开发实战:从架构设计到攻防对抗

    目录 开篇总述&#xff1a;安全视角下的Vue组件开发新范式 一、Vue.js组件开发现状全景扫描 二、安全驱动的Vue组件创新架构 三、工程化组件体系构建指南 四、深度攻防对抗实战解析 五、安全性能平衡策略 结语&#xff1a;安全基因注入前端开发的未来展望 下期预告&…...

    质因数之和-蓝桥20249

    题目&#xff1a; 代码&#xff1a;无脑直接根据题目&#xff0c;一步步操作就行 #include <iostream> using namespace std;int gcd(int a,int b){if(b0) return a;return gcd(b,a%b); }bool exist_gcd(int a,int b){if(gcd(a,b)1) return false;return true; }bool is…...

    《栈区、堆区和静态区:内存管理的三大支柱》

    &#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 一、栈区&#xff08;Stack&#xff09;&#xff08;一&#xff09;栈区的定义&#xff08;二&#xff09;栈区的特点&#xff08;三&#xff09;栈区的使用…...

    Rust Command无法执行*拓展解决办法

    async fn run_cmd_async_out<I, S>(cmd: &str, args: I, timeout_s: u64, with_http_proxy: bool) -> Result<String> whereI: IntoIterator<Item S>,S: AsRef<OsStr>, {let mut cmd tokio::process::Command::new(cmd);// 让 sh 来运行命令&…...

    AI 笔记 - 开源轻量级人脸检测项目

    开源轻量级人脸检测项目 引言项目解析[libfacedetection 于仕琪](https://github.com/ShiqiYu/libfacedetection)[Ultra-Light-Fast-Generic-Face-Detector-1MB Linzaer](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB)[A-Light-and-Fast-Face-Detec…...

    AI Agent vs 大模型

    一句话概述 大模型是“超级学霸”&#xff0c;负责理解、思考和生成内容&#xff1b; AI Agent是“行动派秘书”&#xff0c;会调用工具和知识库&#xff0c;自主完成任务。 角色定位对比 大模型 智能体 核心 任务 回答、创作、推理、分析 规划、决策、执行、协调多工具 …...

    go游戏后端开发32:自摸杠处理逻辑

    当我们在自摸杠时&#xff0c;实际上在杠完之后&#xff0c;我们还需要进行一个删除操作。因此&#xff0c;我们需要在上面拷贝一个删除操作。由于这是自摸杠&#xff0c;所以这个地方需要删除四次。在这里&#xff0c;我们需要注意的是&#xff0c;自摸杠时&#xff0c;传过来…...

    今日行情明日机会——20250411

    今天缩量&#xff0c;上方压力依然在&#xff0c;外围还在升级&#xff0c;企稳还需要时日。 2025年4月11日A股涨停主要行业方向分析 一、核心主线方向 芯片&#xff08;半导体&#xff09; • 涨停家数&#xff1a;24家&#xff08;当日最强方向&#xff09;。 • 驱动逻辑&…...

    【Linux】TCP_Wrappers+iptables实现堡垒机功能

    规划 显示jumpserver的简单功能&#xff0c;大致的网络拓扑图如下 功能规划 & 拓扑结构 JumpServer&#xff08;堡垒机&#xff09;主要功能&#xff1a; 对访问目标服务器进行统一入口控制&#xff08;例如 nginx、mysql、redis&#xff09;。使用 iptables 做 NAT 转…...

    git仓库中.git文件夹过大的问题

    由于git仓库中存放了较大的文件&#xff0c;之后即使在gitignore中添加&#xff0c;也不会导致.git文件夹变小。 参考1 2 通过 du -d 1 -h查看文件大小 使用 git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail…...

    ssh 登录报错集合(FQA)

    1、使用root登录失败&#xff08;远程主机不允许root登录&#xff09; 问题&#xff1a;通过 ssh 连接远程主机&#xff08; ubuntu &#xff09;时报错&#xff0c;Permission denied 如下&#xff1a; 解决方法&#xff1a;确认root的登录密码没错&#xff0c;且可以正常与远…...

    NO.89十六届蓝桥杯备战|动态规划-分组背包-混合背包-多维费用背包|通天之分组背包|排兵布阵|樱花|L国的战斗间谍(C++)

    P1757 通天之分组背包 - 洛谷 因为⼀个组⾥⾯最多只能挑⼀个元素&#xff0c;所以我们就以⼀个组为单位。 状态表⽰&#xff1a; dp[i][j]表⽰从前i 组中挑选物品&#xff0c;总重量不超过j 的情况下&#xff0c;最⼤的价值。 那么dp[n][m]就是最终结果。状态转移⽅程&#x…...

    NVIDIA H100 vs A100:新一代GPU架构性能对比分析

    一、核心架构演进对比 ‌Ampere架构&#xff08;A100&#xff09;‌采用台积电7nm工艺&#xff0c;集成540亿晶体管&#xff0c;配备6,912个CUDA核心和432个第三代Tensor Core&#xff0c;支持FP16、TF32和INT8精度计算。其显存子系统采用HBM2e技术&#xff0c;80GB版本带宽可…...

    使用Mybatis时在XML中SQL高亮显示的方法

    如图所示&#xff0c;上方的SQL代码很像是一个字符串&#xff0c;那么如何把上方的SQL改成和下方一样的SQL,使得IDEA可以识别SQL方言呢&#xff1f; 1.选中SQL中的一部分代码&#xff0c;此时左侧会出现一个黄色的灯泡图案&#xff0c;点击2.选择这个注入语言或者引用...

    机场跑道异物检测数据集VOC+YOLO格式33793张31类别

    数据集分辨率都是300x300,都是贴近地面拍摄&#xff0c;具体看图片 据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;33793 标注数量(xml文件…...

    掌握C语言文件操作:从理论到实战指南

    文件操作是C语言编程中不可或缺的一部分&#xff0c;它使得程序能够持久化存储数据&#xff0c;并在需要时高效读写。本文将从基础概念到实战技巧&#xff0c;系统讲解C语言文件操作的核心知识点&#xff0c;并结合代码示例帮助读者深入理解。 一. 为什么需要文件操作&#xf…...

    如何进行预算考核

    ✅ 一、预算考核体系总体架构 模块内容说明考核内容1. 预算目标/指标完成情况2. 预算编制/执行情况双轮驱动,目标 + 执行双考核考核对象高层、中层、基层、后台支持分层分类考核考核周期月度(滚动)+ 季度(校验)+ 年度(决算)提高适应性和准确性考核工具指标体系、差错率评…...

    在 Linux 上安装 MongoDB Shell

    1. 下载 MongoDB Shell Download | MongoDB wget https://downloads.mongodb.com/compass/mongosh-2.5.0-linux-x64.tgz 2. tar -zxvf mongosh-2.5.0-linux-x64.tgz 3. copy 命令 sudo cp mongosh /usr/local/bin/ sudo cp mongosh_crypt_v1.so /usr/local/lib/ 4. …...

    数据结构-复杂度详解

    前言&#xff1a;大家好&#xff01;本文带来的是数据结构-复杂度的讲解&#xff0c;一起来看看吧&#xff01; 1.算法的时间复杂度和空间复杂度 1.1算法的效率 复杂度&#xff1a;衡量一个算法的好坏&#xff08;效率&#xff09;&#xff0c;从两个维度衡量&#xff0c;时…...

    安宝特新闻丨Vuzix Core™波导助力AR,视角可调、高效传输,优化开发流程

    Vuzix Core™ 光波导技术 近期&#xff0c;Vuzix Core™光波导技术赋能AR新视界&#xff01;该系列镜片支持定制化宽高比调节及20至40视场角范围&#xff0c;可灵活适配各类显示引擎。通过创新的衍射光波导架构&#xff0c;Vuzix Core™实现了光学传输效率与图像质量的双重突破…...

    【SQL】常见SQL 行列转换的方法汇总 - 精华版

    【SQL】常见SQL 行列转换的方法汇总 - 精华版 一、引言二、SQL常见的行列转换对比1. 行转列 Pivoting1.1 ​​CASE WHEN 聚合函数​​1.2 ​​IF 聚合函数​​1.3 ​​PIVOT操作符​​ 2.列转行 Unpivoting2.1 UNION ALL​​2.2 ​​EXPLODE函数&#xff08;Hive/Spark&#…...

    【原创】vue-element-admin-plus完成确认密码功能,并实时获取Form中表单字段中的值

    前言 我第一句就想说&#xff1a;vue-element-admin-plus真是个大坑货&#xff01;就一个确认密码功能都值得我单开一页博客来讲这么一个简单的功能 布局和代码 布局如图所示&#xff0c;我需要密码和确认密码&#xff0c;确认密码需要和密码中的内容一致&#xff0c;不然会返…...

    Vue3中watch监视reactive对象方法详解

    在Vue3中&#xff0c;使用watch监视reactive对象时&#xff0c;需根据监视的目标选择合适的方法。以下是详细的步骤和说明&#xff1a; 1. 监视整个reactive对象 自动深度监视&#xff1a;直接监视reactive对象时&#xff0c;Vue3会默认启用深度监视&#xff0c;无需设置deep:…...

    PyTorch实现多输入输出通道的卷积操作

    本文通过代码示例详细讲解如何在PyTorch中实现多输入通道和多输出通道的卷积运算&#xff0c;并对比传统卷积与1x1卷积的实现差异。 1. 多输入通道互相关运算 当输入包含多个通道时&#xff0c;卷积核需要对每个通道分别进行互相关运算&#xff0c;最后将结果相加。以下是实现…...

    MySQL---数据库基础

    1.数据库概念 文件保存数据有以下几个缺点&#xff1a; 文件的安全性问题 文件不利于数据查询和管理 文件不利于存储海量数据 文件在程序中控制不方便 数据库存储介质&#xff1a; 1.磁盘 2.内存 为了解决上述问题&#xff0c;设计出更加利于管理数据的东西 —— 数据库。…...

    leetcode68.左右文本对齐

    思路源自 leetcode-字符串篇 68题 文本左右对齐 难度高的模拟类型题目&#xff0c;关键点在于事先知道有多少单词要放在本行并且还要知道本行是不是最后一行&#xff08;最后一行需要全部单空格右对齐&#xff0c;不是最后一行就空格均摊&#xff09;&#xff0c;非最后一行的空…...

    若依微服务集成Flowable仿钉钉工作流

    项目简介 本项目工作流模块集成在若依项目单独一个模块&#xff0c;可实现单独运行部署&#xff0c; 前端采用微前端&#xff0c;嵌入在若依的前端项目中。因博主是后端开发&#xff0c;对前端不是太属性&#xff0c;没将工作流模块前端代码移到若依前端。下面贴上代码工程结构…...

    MySQL 架构设计:数据库的“城市规划指南“

    就像一座完美城市需要精心的规划才能高效运行&#xff0c;一个优秀的 MySQL 系统也需要精心的架构设计才能支撑业务的发展…让我们一起探索 MySQL 的"城市规划"&#xff0c;学习如何设计一个既高效又稳定的数据库王国&#xff01; 什么是 MySQL 架构设计&#xff1f…...