> ## Documentation Index
> Fetch the complete documentation index at: https://docs.akria.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Day 02 | 流程控制与 CRUD

> Shell 条件判断/循环与 SQL 基础增删改查

<Info>
  **今日目标**：让脚本具备"逻辑判断"能力，并掌握数据库最核心的 CRUD（增删改查）操作。今天不只是写代码，而是要彻底理解 **"如何让脚本根据条件执行不同操作"**、**"如何高效地操作数据库数据"** 以及 **"为什么 UPDATE 和 DELETE 必须加 WHERE 条件"**。这是编写自动化运维工具的第一步。
</Info>

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="book" size={26} color="#059669" /> 学习内容 (30 mins)</span>

在开始写代码前，先搞懂这些核心概念，否则后面的代码你会看得云里雾里。

<AccordionGroup>
  <Accordion title="Shell 流程控制 (15 mins)" icon="terminal">
    **什么是流程控制？**

    流程控制让脚本能够根据条件执行不同的操作，就像"如果...那么...否则"的逻辑。这是编写自动化脚本的核心能力。

    **为什么需要流程控制？**

    * **条件执行**：根据文件是否存在、服务是否运行等条件执行不同操作
    * **批量处理**：循环处理多个文件、多个服务器
    * **错误处理**：检测错误并采取相应措施
    * **自动化决策**：让脚本能够"思考"和"决策"

    **条件判断详解**：

    * **文件测试**：
      * `[ -f "file.txt" ]`：判断文件是否存在且为普通文件
      * `[ -d "dir" ]`：判断目录是否存在
      * `[ -r "file.txt" ]`：判断文件是否可读
      * `[ -w "file.txt" ]`：判断文件是否可写
      * `[ -x "file.txt" ]`：判断文件是否可执行
    * **字符串比较**：
      * `[ "$name" == "admin" ]`：判断字符串是否相等
      * `[ "$name" != "admin" ]`：判断字符串是否不相等
      * `[ -z "$name" ]`：判断字符串是否为空
      * `[ -n "$name" ]`：判断字符串是否非空
      * **注意**：字符串比较必须用双引号包裹变量，防止空变量导致语法错误
    * **数值比较**：
      * `[ $num -eq 10 ]`：等于（equal）
      * `[ $num -ne 10 ]`：不等于（not equal）
      * `[ $num -gt 10 ]`：大于（greater than）
      * `[ $num -lt 10 ]`：小于（less than）
      * `[ $num -ge 10 ]`：大于等于（greater or equal）
      * `[ $num -le 10 ]`：小于等于（less or equal）
    * **逻辑运算符**：
      * `[ ! -f "file.txt" ]`：非（NOT），文件不存在
      * `[ -f "file.txt" ] && [ -r "file.txt" ]`：与（AND），文件存在且可读
      * `[ -f "file.txt" ] || [ -d "dir" ]`：或（OR），文件存在或目录存在

    **循环结构详解**：

    * **for 循环**：
      * `for i in {1..5}; do ... done`：遍历数字序列
      * `for file in *.log; do ... done`：遍历文件列表
      * `for user in alice bob charlie; do ... done`：遍历字符串列表
      * **应用场景**：批量处理文件、遍历数组、重复执行操作
    * **while 循环**：
      * `while [ condition ]; do ... done`：条件为真时循环
      * **应用场景**：监控服务状态、读取文件行、等待条件满足
    * **until 循环**：
      * `until [ condition ]; do ... done`：条件为假时循环
      * **应用场景**：等待服务启动、等待文件创建
  </Accordion>

  <Accordion title="SQL CRUD (15 mins)" icon="database">
    **什么是 CRUD？**

    CRUD 是数据库操作的四个基本动作：

    * **C**reate（创建）：插入新数据
    * **R**ead（读取）：查询数据
    * **U**pdate（更新）：修改数据
    * **D**elete（删除）：删除数据

    **为什么 CRUD 是数据库的核心？**

    * 所有数据库操作都基于这四个动作
    * 理解 CRUD 是学习复杂查询的基础
    * 掌握 CRUD 能处理 80% 的日常数据库操作

    **INSERT（插入）详解**：

    * **单条插入**：
      ```sql theme={null}
      INSERT INTO users (username, email) VALUES ('alice', 'alice@example.com');
      ```
      * 指定字段：只插入需要的字段，其他字段使用默认值
      * 不指定字段：必须提供所有字段的值（不推荐）
    * **批量插入**：
      ```sql theme={null}
      INSERT INTO users (username, email) VALUES 
      ('alice', 'alice@example.com'),
      ('bob', 'bob@example.com'),
      ('charlie', 'charlie@example.com');
      ```
      * 优势：一次插入多条数据，效率更高
      * 应用场景：初始化数据、批量导入

    **SELECT（查询）详解**：

    * **基础查询**：
      ```sql theme={null}
      SELECT * FROM users;  -- 查询所有字段
      SELECT id, username FROM users;  -- 查询指定字段
      ```

    * **WHERE 条件**：
      ```sql theme={null}
      SELECT * FROM users WHERE status = 1;  -- 查询活跃用户
      SELECT * FROM users WHERE username = 'alice';  -- 精确匹配
      SELECT * FROM users WHERE created_at > '2024-01-01';  -- 日期比较
      ```
      * `=`：等于
      * `!=` 或 `<>`：不等于
      * `>`、`<`、`>=`、`<=`：比较运算符
      * `LIKE`：模糊匹配（后续课程会讲）
      * `IN`：在列表中
      * `AND`、`OR`：逻辑运算符

    **UPDATE（更新）详解**：

    * **基本语法**：
      ```sql theme={null}
      UPDATE users SET email = 'new@example.com' WHERE username = 'alice';
      ```

    * **为什么必须加 WHERE？**
      * 不加 WHERE：会更新**所有**记录（非常危险！）
      * 加 WHERE：只更新符合条件的记录
      * **最佳实践**：更新前先用 SELECT 查询确认要更新的记录

    * **更新多个字段**：
      ```sql theme={null}
      UPDATE users 
      SET email = 'new@example.com', status = 0 
      WHERE username = 'alice';
      ```

    **DELETE（删除）详解**：

    * **物理删除**：
      ```sql theme={null}
      DELETE FROM users WHERE username = 'charlie';
      ```
      * 数据从数据库中永久删除，无法恢复
      * 适用于：测试数据、临时数据
    * **软删除（推荐）**：
      ```sql theme={null}
      UPDATE users SET status = 0 WHERE username = 'charlie';
      ```
      * 不删除数据，只标记为"已删除"
      * 优势：可以恢复数据、保留历史记录
      * 适用于：生产环境、重要数据
    * **为什么必须加 WHERE？**
      * 不加 WHERE：会删除**所有**记录（灾难性错误！）
      * 加 WHERE：只删除符合条件的记录
      * **最佳实践**：删除前先用 SELECT 查询确认要删除的记录
  </Accordion>
</AccordionGroup>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="code" size={26} color="#059669" /> 代码任务 (90 mins)</span>

<Steps>
  <Step title="Shell 逻辑脚本">
    编写 `02_monitor.sh`，模拟简单的服务监控：

    **任务分解**：

    1. 定义日志目录变量
    2. 判断目录是否存在
    3. 如果不存在，创建目录
    4. 循环生成 5 个日志文件
    5. 每个日志文件包含服务器状态信息

    <CodeGroup>
      ```bash 02_monitor.sh theme={null}
      #!/bin/bash
      # 02_monitor.sh - 模拟服务监控脚本

      # 定义日志目录
      # 使用相对路径，脚本所在目录下的 logs 文件夹
      LOG_DIR="./logs"

      # 条件判断：检查目录是否存在
      # [ -d "$LOG_DIR" ]：判断 $LOG_DIR 是否存在且为目录
      # !：取反，即"如果目录不存在"
      # 注意：变量必须用双引号包裹，防止路径中有空格导致错误
      if [ ! -d "$LOG_DIR" ]; then
          # 如果目录不存在，创建目录
          # mkdir -p：递归创建目录，如果父目录不存在也会创建
          # -p 参数很重要：不会因为目录已存在而报错
          echo "Creating log directory..."
          mkdir -p "$LOG_DIR"
      else
          # 如果目录已存在，提示信息
          echo "Log directory already exists."
      fi

      # for 循环：遍历数字序列
      # {1..5}：生成序列 1, 2, 3, 4, 5
      # 这是 Bash 的序列展开语法
      for i in {1..5}; do
          # 构建文件名
          # $LOG_DIR/app_$i.log：例如 ./logs/app_1.log
          filename="$LOG_DIR/app_$i.log"
          
          # 写入日志内容
          # >：覆盖写入（如果文件已存在，会清空后写入）
          # 如果要用追加模式，使用 >>（后续课程会讲）
          echo "Server $i status: OK" > "$filename"
          
          # 输出创建信息
          echo "Created $filename"
      done

      # 循环结束后，输出总结信息
      echo "All log files created successfully."
      ```
    </CodeGroup>

    **常见错误**：

    <Warning>
      * ❌ `if [ ! -d $LOG_DIR ]` - 变量未加引号，路径有空格会报错
      * ❌ `for i in 1 2 3 4 5` - 可以工作，但不如 `{1..5}` 简洁
      * ❌ `mkdir $LOG_DIR` - 如果目录已存在会报错，应该用 `mkdir -p`
      * ✅ `if [ ! -d "$LOG_DIR" ]` - 正确写法
      * ✅ `for i in {1..5}` - 正确写法
      * ✅ `mkdir -p "$LOG_DIR"` - 正确写法
    </Warning>

    **验证步骤**：

    1. 保存文件后，添加执行权限：
       ```bash theme={null}
       chmod +x 02_monitor.sh
       ```

    2. 运行脚本：
       ```bash theme={null}
       ./02_monitor.sh
       ```

    3. 检查输出：
       * 应该看到 "Creating log directory..." 或 "Log directory already exists."
       * 应该看到 5 个文件创建信息
       * 应该看到 "All log files created successfully."

    4. 验证文件：
       ```bash theme={null}
       ls -la logs/
       cat logs/app_1.log
       ```

    5. 测试边界情况：
       * 删除 logs 目录后再次运行脚本
       * 手动创建 logs 目录后运行脚本
  </Step>

  <Step title="SQL 数据操作">
    编写 `02_crud.sql`，对昨日的 `users` 表进行操作：

    **任务分解**：

    1. 批量插入测试数据
    2. 查询活跃用户
    3. 更新用户邮箱
    4. 删除用户（演示物理删除，实际生产环境建议用软删除）

    <CodeGroup>
      ```sql 02_crud.sql theme={null}
      -- ============================================
      -- Day 02: SQL CRUD 操作实战
      -- ============================================

      -- 确保使用正确的数据库
      USE infra_db;

      -- ============================================
      -- 1. INSERT（插入数据）
      -- ============================================

      -- 批量插入用户数据
      -- 注意：只指定 username 和 email，其他字段使用默认值
      -- status 会使用默认值 1（启用）
      -- created_at 会使用默认值 CURRENT_TIMESTAMP（当前时间）
      INSERT INTO users (username, email) VALUES 
      ('Alice', 'alice@example.com'),
      ('Bob', 'bob@example.com'),
      ('Charlie', 'charlie@example.com');

      -- 验证插入结果
      SELECT * FROM users;

      -- ============================================
      -- 2. SELECT（查询数据）
      -- ============================================

      -- 查询所有用户的所有字段
      SELECT * FROM users;

      -- 查询指定字段
      -- 只查询 id、username、created_at 三个字段
      SELECT id, username, created_at FROM users;

      -- 查询活跃用户（status = 1）
      -- WHERE 子句用于过滤数据
      -- = 表示等于，只返回 status 为 1 的记录
      SELECT id, username, created_at 
      FROM users 
      WHERE status = 1;

      -- 查询特定用户
      SELECT * FROM users WHERE username = 'Alice';

      -- ============================================
      -- 3. UPDATE（更新数据）
      -- ============================================

      -- ⚠️ 重要：UPDATE 必须加 WHERE 条件！
      -- 不加 WHERE 会更新所有记录，这是非常危险的操作

      -- 更新 Bob 的邮箱
      -- 先查询确认要更新的记录
      SELECT * FROM users WHERE username = 'Bob';

      -- 执行更新
      UPDATE users 
      SET email = 'bob_new@example.com' 
      WHERE username = 'Bob';

      -- 验证更新结果
      SELECT * FROM users WHERE username = 'Bob';

      -- ============================================
      -- 4. DELETE（删除数据）
      -- ============================================

      -- ⚠️ 重要：DELETE 必须加 WHERE 条件！
      -- 不加 WHERE 会删除所有记录，这是灾难性错误！

      -- 先查询确认要删除的记录
      SELECT * FROM users WHERE username = 'Charlie';

      -- 物理删除（不推荐在生产环境使用）
      -- 注意：这里演示物理删除，实际生产环境建议使用软删除
      -- 软删除：UPDATE users SET status = 0 WHERE username = 'Charlie';
      DELETE FROM users WHERE username = 'Charlie';

      -- 验证删除结果
      SELECT * FROM users;
      ```
    </CodeGroup>

    **字段类型选择详解**：

    <AccordionGroup>
      <Accordion title="UPDATE 为什么必须加 WHERE？">
        **危险示例**：

        ```sql theme={null}
        -- ❌ 错误：没有 WHERE 条件
        UPDATE users SET email = 'hacked@example.com';
        -- 结果：所有用户的邮箱都被修改！
        ```

        **正确做法**：

        ```sql theme={null}
        -- ✅ 正确：有 WHERE 条件
        UPDATE users SET email = 'new@example.com' WHERE username = 'Alice';
        -- 结果：只更新 Alice 的邮箱
        ```

        **最佳实践**：

        1. 更新前先用 SELECT 查询确认要更新的记录
        2. 在 WHERE 条件中使用唯一字段（如主键、用户名）
        3. 生产环境建议使用事务（后续课程会讲）
      </Accordion>

      <Accordion title="DELETE 为什么必须加 WHERE？">
        **危险示例**：

        ```sql theme={null}
        -- ❌ 错误：没有 WHERE 条件
        DELETE FROM users;
        -- 结果：所有用户数据都被删除！无法恢复！
        ```

        **正确做法**：

        ```sql theme={null}
        -- ✅ 正确：有 WHERE 条件
        DELETE FROM users WHERE username = 'Charlie';
        -- 结果：只删除 Charlie 的记录
        ```

        **最佳实践**：

        1. 删除前先用 SELECT 查询确认要删除的记录
        2. 生产环境建议使用软删除（UPDATE status = 0）
        3. 重要数据删除前先备份
      </Accordion>

      <Accordion title="软删除 vs 硬删除">
        **硬删除（物理删除）**：

        * 使用 `DELETE FROM` 命令
        * 数据从数据库中永久删除
        * 无法恢复
        * 适用于：测试数据、临时数据

        **软删除（逻辑删除）**：

        * 使用 `UPDATE` 命令修改状态字段
        * 数据仍然存在，只是标记为"已删除"
        * 可以恢复
        * 适用于：生产环境、重要数据

        **示例**：

        ```sql theme={null}
        -- 软删除：标记为禁用
        UPDATE users SET status = 0 WHERE username = 'Charlie';

        -- 查询时排除已删除的数据
        SELECT * FROM users WHERE status = 1;

        -- 恢复数据
        UPDATE users SET status = 1 WHERE username = 'Charlie';
        ```
      </Accordion>
    </AccordionGroup>

    **验证步骤**：

    1. 连接 MySQL：
       ```bash theme={null}
       docker exec -it mysql-learn mysql -uroot -proot
       ```

    2. 执行 SQL 脚本：
       ```sql theme={null}
       USE infra_db;
       source /path/to/02_crud.sql;
       ```
       或者直接复制粘贴 SQL 语句执行

    3. 验证 INSERT：
       ```sql theme={null}
       SELECT * FROM users;
       -- 应该看到 3 条新插入的记录
       ```

    4. 验证 UPDATE：
       ```sql theme={null}
       SELECT * FROM users WHERE username = 'Bob';
       -- 应该看到 Bob 的邮箱已更新
       ```

    5. 验证 DELETE：
       ```sql theme={null}
       SELECT * FROM users;
       -- 应该看不到 Charlie 的记录
       ```

    6. 测试危险操作（仅限测试环境）：
       ```sql theme={null}
       -- 先备份数据
       CREATE TABLE users_backup AS SELECT * FROM users;

       -- 测试无 WHERE 的 UPDATE（危险！）
       -- UPDATE users SET email = 'test@example.com';
       -- 注意：不要在生产环境执行！
       ```

    **常见错误**：

    <Warning>
      * ❌ `UPDATE users SET email = 'new@example.com'` - 没有 WHERE，会更新所有记录
      * ❌ `DELETE FROM users` - 没有 WHERE，会删除所有记录
      * ❌ `INSERT INTO users VALUES (...)` - 不指定字段，必须提供所有字段的值
      * ❌ `SELECT * FROM users WHERE username = Alice` - 字符串未加引号，会被当作列名
      * ✅ `UPDATE users SET email = 'new@example.com' WHERE username = 'Alice'` - 正确
      * ✅ `DELETE FROM users WHERE username = 'Charlie'` - 正确
      * ✅ `INSERT INTO users (username, email) VALUES ('Alice', 'alice@example.com')` - 正确
      * ✅ `SELECT * FROM users WHERE username = 'Alice'` - 正确
    </Warning>
  </Step>
</Steps>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="lightbulb" size={26} color="#059669" /> 拓展任务 (30 mins)</span>

<CardGroup cols={2}>
  <Card title="Shell 挑战" icon="terminal">
    **任务**：学习 `while` 循环，写一个脚本每秒输出当前时间，持续 5 秒后退出。

    **提示**：

    * 使用 `while` 循环和计数器
    * 使用 `date` 命令获取当前时间
    * 使用 `sleep 1` 暂停 1 秒
    * 思考：如何让循环在 5 秒后自动退出？
  </Card>

  <Card title="SQL 挑战" icon="database">
    **任务**：学习 `LIMIT`（分页）和 `ORDER BY`（排序）的用法。

    **提示**：

    * 使用 `ORDER BY created_at DESC` 按创建时间倒序排列
    * 使用 `LIMIT 10` 限制返回 10 条记录
    * 思考：如何实现分页查询（第 1 页、第 2 页）？
  </Card>
</CardGroup>

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="file" size={26} color="#059669" /> 今日产出物</span>

* `day02/02_monitor.sh` - Shell 监控脚本
* `day02/02_crud.sql` - SQL CRUD 操作脚本

***

## <span style={{ display: 'inline-flex', alignItems: 'center', gap: '8px' }}><Icon icon="github" size={26} color="#059669" /> 参考代码</span>

<CardGroup cols={2}>
  <Card title="查看参考代码" icon="code" href="https://github.com/akriamail/insightful-ops/tree/main/docs/mintlify/scripts/training/30daystudy/week-1/day-02">
    在 GitHub 查看完整的示例代码

    <br />

    <small>包含 Shell 脚本和 SQL 文件</small>
  </Card>

  <Card title="在线运行" icon="play" href="https://replit.com">
    使用在线编辑器测试代码

    <br />

    <small>无需本地环境配置</small>
  </Card>
</CardGroup>

***

## 实际应用场景

<CardGroup cols={2}>
  <Card title="Shell 流程控制在运维中的应用" icon="server">
    * **服务监控**：检查服务状态，异常时自动重启
    * **文件管理**：批量处理日志文件，自动清理过期文件
    * **部署脚本**：根据环境变量执行不同的部署流程
    * **备份脚本**：循环备份多个数据库或目录
    * **健康检查**：定期检查服务健康状态，发送告警
  </Card>

  <Card title="SQL CRUD 在生产环境的最佳实践" icon="database">
    * **INSERT**：使用批量插入提高效率，避免逐条插入
    * **SELECT**：只查询需要的字段，避免 `SELECT *`
    * **UPDATE**：必须加 WHERE 条件，更新前先查询确认
    * **DELETE**：生产环境使用软删除，保留数据可恢复
    * **事务管理**：重要操作使用事务保证数据一致性（后续课程会讲）
  </Card>
</CardGroup>

<Tip>
  **与 Day 01 和 Day 03 的关联**：

  * Day 01 学习了基础的变量和表结构，今天学习了如何使用流程控制操作这些数据
  * Day 03 将学习更高级的 Shell 管道和 SQL 联表查询，今天的 CRUD 操作是基础
  * 掌握今天的流程控制和 CRUD，明天的学习会更轻松
</Tip>

***

<CardGroup cols={2}>
  <Card title="上一天: 基础入门" href="/training/30daystudy/day-01">
    Day 01 | Shell 基础入门与建表
  </Card>

  <Card title="下一天: 管道联表" href="/training/30daystudy/day-03">
    Day 03 | Shell 管道与 SQL 联表查询
  </Card>
</CardGroup>
