MySQL – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Tue, 16 Jun 2026 07:15:05 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 阿里云RDS数据库误操作损毁,完整备份恢复实操避坑指南 https://www.shuijingwanwq.com/2026/06/16/17177/ https://www.shuijingwanwq.com/2026/06/16/17177/#respond Tue, 16 Jun 2026 07:15:03 +0000 https://www.shuijingwanwq.com/?p=17177 Post Views: 11

一、事故背景
本次调试WordPress多语言标签同步脚本过程中,脚本内新增批量查询并删除脏标签的逻辑,海量数据循环处理下脚本运行卡顿卡死。强制终止进程后,数据库出现不可逆的严重损毁:
参考:WP 6.9 标签同步脚本在 WP 7.0 失效完整排查与解决实录

  • 大量正常英文标签被误判定为脏数据删除,英文标签总量从八千多条直接暴跌至仅剩2条(如图1);
  • 中文标签产生百余条重复冗余数据,统计数量异常上涨;
  • 库内遗留无语言归属脏数据,多语言翻译关联关系破碎错乱,网站后台统计、标签管理功能彻底异常。
大量正常英文标签被误判定为脏数据删除,英文标签总量从八千多条直接暴跌至仅剩2条(如图1)
大量正常英文标签被误判定为脏数据删除,英文标签总量从八千多条直接暴跌至仅剩2条(如图1)

日常习惯通过DMS手动导出SQL文件做离线备份,本次离线备份为数日前历史数据,无法还原半天内最新业务数据;同时RDS实例级备份同样存在时间差,且实例整体恢复需要额外购置实例资源,不符合本次仅修复单库数据、不改动实例架构的需求。最终选用库表时间点恢复功能,精准回退到故障发生前半天的时间节点,顺利找回完整有效数据。

二、前置准备工作

  1. 确认目标还原时间,锁定脚本异常执行之前半小时左右的时间点;
  2. 阿里云RDS MySQL实例运行正常,本地网络已加入实例白名单;
  3. 暂停网站前端访问、关闭服务器所有正在运行的数据库脚本,杜绝恢复过程产生新数据写入冲突;
  4. 提前记录网站wp-config.php原有数据库名称、账号权限信息,便于后续切换数据库配置。

三、贴合实际场景的恢复流程:库表时间点还原
数据错乱程度高,历史离线SQL备份、实例整机备份均时效性不足,放弃删库重建、整机实例恢复方案,采用RDS原生按时间点库表恢复,精准回溯指定时段数据。

  1. 进入库表恢复功能入口
    1.1. 登录阿里云官网,进入云数据库RDS控制台,点开当前运行的数据库实例详情页;
    1.2. 左侧导航栏找到备份恢复板块,点击进入后选择库表恢复功能;
    1.3. 恢复方式选定按时间点恢复,手动选取故障触发之前半天的准确时间戳(如图2)。
  2. 选定待恢复数据库与数据表
    2.1. 在数据库列表中勾选本次受损站点数据库 shuijingwanwq
    2.2. 重点避坑:仅勾选数据库无法完成数据还原,选中库之后,右侧数据表列表点击全选,囊括库内所有数据表(如图3);
    2.3. 确认恢复目标,系统默认生成后缀为_backup的全新备份库,不会直接覆盖线上原有数据库,安全无风险。
  3. 提交恢复任务,查看执行进度
    3.1. 核对还原时间、库表范围无误后,提交库表恢复任务;
    3.2. 页面跳转至任务中心,实时查看任务状态,等待系统后台自动完成数据回溯还原(如图4);
    3.3. 任务显示执行成功,代表对应时间节点的完整数据已生成在备份库内(如图5)。
  4. 分配数据库访问权限
    4.1. 回到数据库管理页面,找到恢复生成的新库 shuijingwanwq_backup(如图6);
    4.2. 将网站程序原有数据库访问账号,添加赋予该备份库完整读写权限(如图7);
    4.3. 校验账号连通性,确保程序能够正常读取、操作新库数据(如图8)。
  5. 修改站点配置切换数据库
    5.1. 登录网站服务器,打开网站根目录下 wp-config.php 配置文件;
    5.2. 将原数据库名称参数,修改为恢复后的库名 shuijingwanwq_backup(如图9);
    5.3. 保存配置文件,无需改动数据库账号、密码、地址等其余参数。
恢复方式选定按时间点恢复,手动选取故障触发之前半天的准确时间戳(如图2)
恢复方式选定按时间点恢复,手动选取故障触发之前半天的准确时间戳(如图2)
重点避坑:仅勾选数据库无法完成数据还原,选中库之后,右侧数据表列表点击全选,囊括库内所有数据表(如图3)
重点避坑:仅勾选数据库无法完成数据还原,选中库之后,右侧数据表列表点击全选,囊括库内所有数据表(如图3)
页面跳转至任务中心,实时查看任务状态,等待系统后台自动完成数据回溯还原(如图4)
页面跳转至任务中心,实时查看任务状态,等待系统后台自动完成数据回溯还原(如图4)
任务显示执行成功,代表对应时间节点的完整数据已生成在备份库内(如图5)
任务显示执行成功,代表对应时间节点的完整数据已生成在备份库内(如图5)
回到数据库管理页面,找到恢复生成的新库 shuijingwanwq_backup(如图6)
回到数据库管理页面,找到恢复生成的新库 shuijingwanwq_backup(如图6)
将网站程序原有数据库访问账号,添加赋予该备份库完整读写权限(如图7)
将网站程序原有数据库访问账号,添加赋予该备份库完整读写权限(如图7)
校验账号连通性,确保程序能够正常读取、操作新库数据(如图8)
校验账号连通性,确保程序能够正常读取、操作新库数据(如图8)
将原数据库名称参数,修改为恢复后的库名 shuijingwanwq_backup(如图9)
将原数据库名称参数,修改为恢复后的库名 shuijingwanwq_backup(如图9)

四、恢复后数据校验与收尾

  1. 刷新数据库表结构,核对数据表数量、字段结构与正常状态一致;
  2. 进入WordPress后台,分别查看中文、英文标签统计数量,恢复为故障前标准数值8271条(如图10);
  3. 抽查标签翻译关联、文章内容、站点基础配置,确认无缺失、错乱、重复数据;
  4. 进入W3 Total Cache插件仪表盘,一键清空全站缓存,刷新网站前台页面,访问浏览、功能操作全部恢复正常。
进入WordPress后台,分别查看中文、英文标签统计数量,恢复为故障前标准数值8271条(如图10)
进入WordPress后台,分别查看中文、英文标签统计数量,恢复为故障前标准数值8271条(如图10)

五、本次RDS恢复实操总结与避坑要点

  1. 离线SQL备份、实例整机备份存在时间滞后,近期数据损毁优先使用时间点库表恢复,无需新增实例,低成本精准还原数据;
  2. 库表恢复务必勾选整库+全部数据表,单独勾选数据库不勾选表,无法生效任何数据恢复;
  3. 系统自动生成备份新库,不会覆盖线上原有数据,操作容错率高,无需担心二次损坏原始数据;
  4. 恢复完成后必须同步配置数据库账号权限,再修改网站数据库参数,避免权限不足导致网站无法连接数据库;
  5. 批量删除、循环查询类高危数据库脚本,运行前优先锁定时间节点,留存可回溯节点,降低数据损毁损失;
  6. 恢复结束清空全站缓存,消除缓存残留带来的数据显示异常,保证后台与前台数据统一。
]]>
https://www.shuijingwanwq.com/2026/06/16/17177/feed/ 0
Ubuntu 26.04 + Docker 搭建 Go Gin 开发环境(全记录) https://www.shuijingwanwq.com/2026/06/03/15587/ https://www.shuijingwanwq.com/2026/06/03/15587/#respond Wed, 03 Jun 2026 11:44:06 +0000 https://www.shuijingwanwq.com/?p=15587 Post Views: 40

从 Windows WSL2 无缝迁移到 Ubuntu 裸机,基于现有 MySQL/Redis 容器构建 Go 开发环境,并接入自定义网络。

📌 前言

之前一直在 Windows 10 + WSL2 中学习 go-gin-learning 项目,现在换到了全新的 Ubuntu 26.04 系统。希望继续使用 Docker 来隔离开发环境,同时复用已经部署好的 MySQL 8.0 和 Redis 7.2 容器(位于自定义网络 services_dev-network 中)。

本文记录了从零开始配置的全部步骤,包含:

  • 系统工具检查与安装
  • Docker 环境验证
  • 项目代码克隆
  • 编写 Dockerfile 预装 git
  • 编写 docker-compose.yml 接入现有网络
  • 容器内运行 Gin 应用并测试连通性
  • 常见问题与避坑建议

🧰 一、准备工作:检查系统已有工具

在 Ubuntu 中养成“先检查,后安装”的习惯,可以避免重复操作。

1.1 检查 Git

git --version

输出示例(已安装):

git version 2.53.0

若未安装,执行:

sudo apt install git -y

1.2 检查 Docker

docker --version
docker compose version

输出示例

Docker version 29.5.2, build 79eb04c
Docker Compose version v5.1.4

若未安装,请参考 Docker 官方文档 安装。

建议:将当前用户加入 docker 组,避免每次输入 sudo

sudo usermod -aG docker $USER

然后注销并重新登录


🌐 二、确认已有数据库网络环境

之前已经在 ~/docker/services 中通过 docker-compose.yml 运行了 MySQL 8.0 和 Redis 7.2,并创建了自定义网络 services_dev-network

2.1 查看网络

docker network ls | grep dev-network

输出

71f462c6ef91   services_dev-network   bridge    local

2.2 查看数据库容器状态

docker ps --filter "name=mysql80" --filter "name=redis72"

输出

CONTAINER ID   IMAGE       ...   NAMES
9ec38ca384b6   redis:7.2   ...   redis72
4e93fd2e2c3f   mysql:8.0   ...   mysql80

💡 关键信息

  • MySQL 容器名:mysql80,内部端口 3306,宿主机映射端口 3307
  • Redis 容器名:redis72,内部端口 6379
  • 网络名:services_dev-network注意前缀,由 ~/docker/services 目录名派生)

📁 三、创建代码目录并克隆项目

选择一个语义清晰的目录存放项目代码,这里使用 ~/code(全小写,符合 Linux 惯例)。

mkdir -p ~/code
cd ~/code
git clone https://github.com/shuijingwan/go-gin-learning.git
cd go-gin-learning

检查项目结构:

ls -la

🐳 四、编写 Dockerfile(预装 git)

为了不在每次进入容器时手动安装 git,我们创建一个自定义镜像。

创建 Dockerfile

FROM golang:1.26-alpine

# 安装 git(go mod 下载某些依赖时需要)
RUN apk add --no-cache git

# 设置工作目录,与后面的 docker-compose 保持一致
WORKDIR /code

# 保持容器运行(开发模式)
CMD ["tail", "-f", "/dev/null"]

📌 说明

  • 使用 Alpine 版本镜像,体积小
  • apk add --no-cache 不保留缓存,减小镜像大小
  • WORKDIR /code 后续可以省略 docker-compose 中的 working_dir,但为了明确,两边都保留也没问题

⚙️ 五、编写 docker-compose.yml(接入现有网络)

关键点:

  • 使用 build: . 而不是 image: ...
  • 加入外部网络 services_dev-network
  • 传递数据库连接所需的环境变量
  • 删除已过时的 version 字段

最终 docker-compose.yml 内容:

services:
  go:
    build: .
    container_name: go-gin-learning
    working_dir: /code
    volumes:
      - ./:/code
    ports:
      - "8080:8080"
    tty: true
    stdin_open: true
    environment:
      - GOPROXY=https://goproxy.io,direct
      - GOSUMDB=sum.golang.google.cn
      - CGO_ENABLED=0
      # 数据库连接配置(使用现有网络中的服务名)
      - DB_HOST=mysql80
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=mystrongpwd80
      - DB_NAME=go_gin
      - REDIS_HOST=redis72
      - REDIS_PORT=6379
      - REDIS_PASSWORD=myredispass
    networks:
      - services_dev-network

networks:
  services_dev-network:
    external: true

⚠️ 注意

  • DB_PASSWORDREDIS_PASSWORD 要与 ~/docker/services/docker-compose.yml 中的密码完全一致
  • 容器间通信使用内部端口 3306 和 6379,而不是宿主机映射的 3307。

🚀 六、构建并启动开发容器

# 停止并删除旧容器(如果有)
docker compose down

# 构建镜像(--no-cache 确保重新安装 git)
docker compose build --no-cache

# 启动容器(后台运行)
docker compose up -d

查看运行状态:

docker ps --filter "name=go-gin-learning"

🧪 七、进入容器并验证连接

7.1 进入容器

docker exec -it go-gin-learning sh

此时提示符变为 /code #

7.2 验证 git 已安装

git --version

应输出类似 git version 2.52.0

7.3 测试网络连通性

# 安装测试工具(仅调试用,正式镜像可不加)
apk add iputils mysql-client redis

# ping 数据库容器
ping -c 2 mysql80
ping -c 2 redis72

7.4 测试 MySQL 连接(禁用 SSL)

MySQL 8.0 默认要求 SSL,但在容器内部使用 --ssl=0 即可绕过证书验证:

mariadb -h mysql80 -P 3306 -u root -pmystrongpwd80 -e "SHOW DATABASES;" --ssl=0

预期输出:如图1

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

💡 若使用 mysql 命令,请用 --ssl-mode=DISABLED

7.5 测试 Redis 连接

redis-cli -h redis72 -a myredispass PING

预期输出:如图2

预期输出:如图2
PONG

🏃 八、运行 Gin 应用

在容器内执行:

go run main.go

看到如下日志表示成功:如图3

看到如下日志表示成功:如图3
go: downloading github.com/go-playground/locales v0.14.1
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /albums                   --> main.getAlbums (3 handlers)
[GIN-debug] GET    /albums/:id               --> main.getAlbumByID (3 handlers)
[GIN-debug] POST   /albums                   --> main.postAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080
[GIN] 2026/06/03 - 11:19:14 | 404 |  1.219µs |      172.18.0.1 | GET      "/"
[GIN] 2026/06/03 - 11:19:14 | 404 |    778ns |      172.18.0.1 | GET      "/favicon.ico"
[GIN] 2026/06/03 - 11:19:45 | 200 | 147.412µs |      172.18.0.1 | GET      "/albums"


在宿主机测试 API

打开另一个终端,执行:

curl http://localhost:8080/albums

或直接在浏览器访问 http://localhost:8080/albums

📸 浏览器显示 JSON 数据,如图4


🧹 九、常用管理命令

操作命令
启动开发容器cd ~/code/go-gin-learning && docker compose up -d
停止容器docker compose down
重新构建镜像docker compose build --no-cache
查看日志docker compose logs -f
进入容器docker exec -it go-gin-learning sh
重启 MySQL/Redis(若需要)cd ~/docker/services && docker compose restart

⚠️ 十、避坑指南

10.1 容器内 git 缺失导致 go run 失败

现象

unable to resolve git version: exec: "git": executable file not found in $PATH

解决:使用自定义 Dockerfile 安装 git,见第四章。

10.2 MySQL 连接报 TLS/SSL 错误

现象

ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure

解决:在连接命令中添加 --ssl=0(mariadb)或 --ssl-mode=DISABLED(mysql)。

10.3 容器无法访问 MySQL/Redis

原因:未加入正确的网络,或网络名称写错。

解决

  • 确认 docker network ls 中有 services_dev-network
  • 检查 docker-compose.yml 中 networks 名称是否完全一致(包括下划线)
  • 在容器内 ping mysql80 测试

10.4 宿主机无法访问 8080 端口

原因:Gin 监听的是 127.0.0.1:8080 而不是 0.0.0.0:8080,导致端口映射失效。

解决:在代码中设置 router.Run(":8080")router.Run("0.0.0.0:8080")


📝 十一、总结

通过本次实践,我实现了:

  1. ✅ 在 Ubuntu 26.04 上搭建完整的 Go 开发环境
  2. ✅ 利用已有的 MySQL/Redis 容器(独立部署)和自定义网络
  3. ✅ 构建带 git 的 Go 1.26 Alpine 开发镜像
  4. ✅ 编写 docker-compose.yml 挂载代码并接入外部网络
  5. ✅ 在容器内成功运行 Gin 应用并验证数据库连通性

这种“数据库与代码分离容器”的模式非常适合多项目共用基础设施,既节省资源,又保持环境一致性。

下一步,便可以:

  • main.go 中编写连接 MySQL/Redis 的业务逻辑
  • 使用任意代码编辑器(如 VS Code、Sublime 等)直接编辑项目文件,保存后容器内实时同步
  • docker-compose.yml 加入版本控制,与队友共享环境配置

🔗 参考资料

]]>
https://www.shuijingwanwq.com/2026/06/03/15587/feed/ 0
从百度API推送到AI IDE选型:我如何搞定Trae安装并准备开工 https://www.shuijingwanwq.com/2026/06/01/15341/ https://www.shuijingwanwq.com/2026/06/01/15341/#respond Mon, 01 Jun 2026 09:08:48 +0000 https://www.shuijingwanwq.com/?p=15341 Post Views: 57

最近在折腾一个 WordPress 站点的百度收录问题,顺便体验了一把国内新出的 AI IDE,把过程记录下来,希望对大家有帮助。

起因:百度 sitemap 提交一直不可用

我的网站 shuijingwanwq.com 在百度搜索资源平台里,sitemap 提交功能一直显示:

今日提交上限:0条
今日提交余额:0条

提示“由于百度的 sitemap 暂不可用”。也就是说,这条路从一开始就走不通。没办法,只能走 API 主动推送 这条路。

我的方案:Gin + MySQL 实现增量提交

网站目前有 1141 篇文章(WordPress 生成的 sitemap 里写的),而且以后每年大概新增 100 篇左右。我的需求很简单:

  • 解析 https://www.shuijingwanwq.com/wp-sitemap-posts-post-1.xml
  • 把里面所有的 URL 通过百度“普通收录 – API 提交”接口推上去
  • 只推未提交过的(增量)
  • 需要一个简单的 Web 界面,能手动触发提交,并展示哪些已经提交、哪些待提交

因为 sitemap 严格按发布时间升序排列,我决定用 MySQL 8.0 记录已提交的 URL,每次从最后一条已提交记录的下一条开始推,逻辑清晰。

选 AI 编程工具:我的纠结与最终选择

作为一个后端开发(Go/PHP 为主,偶尔写前端),平时重度依赖 VS Code,但 GitHub Copilot 免费额度太少,而且总是和通义灵码打架。我希望能找一个没有 Copilot 残留、内置顶级模型、国内支付方便的 AI IDE。

我调研了一圈:

工具特点付费方式是否独立 IDE
Cursor体验最好,内置 Claude/GPT-4o$20/月,需国际信用卡是(基于 VS Code 分支)
通义灵码阿里出品,Java/Go 友好,有独立版个人基础版免费,专业版 ¥59/月有独立 IDE
CodeGeeX智谱开源,可私有部署,完全免费免费插件为主,无独立 IDE
Trae字节跳动出品,国内版内置国产模型,国际版内置 Claude/GPT-4o国内版免费使用 SOLO 模式;国际版 $10/月,支持支付宝是(独立 IDE)

考虑到我习惯 VS Code 键位,又希望彻底告别 Copilot,还想要内置 Claude 级别的模型,最终选择了 Trae 国内版——反正国内版内置的豆包、通义千问、DeepSeek 对付这个项目完全够了。国际版支付需要信用卡或 PayPal,我没有去尝试,以后如果真要用国外模型再说。

安装 Trae 国内版:踩了两个坑

坑一:下载了 arm64 版本,依赖报错

我打开 trae.cn 首页,直接点击“下载 TRAE IDE”,浏览器自动下载了一个文件。我没太注意文件名,就用系统软件安装器打开,结果报了一长串依赖缺失:

依赖: libasound2 (>= 1.0.17) but it is not installable
依赖: libgtk-3-0 (>= 3.9.10) but it is not installable
...

检查了一下,我下载的是 Trae_CN-linux-arm64.deb。再一查系统架构:

dpkg --print-architecture
# 输出 amd64

我的是 x86_64 的 Ubuntu,却下载了 ARM 包。后来去官网手动选了 Trae_CN-linux-x64.deb 重新下载(如图1、图2)。

如图1:下载页面显示 .deb (x64) 选项

如图1:下载页面显示 .deb (x64) 选项


如图2:下载后的文件 Trae_CN-linux-x64.deb

如图2:下载后的文件 Trae_CN-linux-x64.deb

坑二:登录一直卡在浏览器加载中

这次安装很顺利。打开后,软件让我选择编辑器配置——我选了 VS Code,还有另外一个编辑器(记不清名字了),然后点击“个人用户”进行登录。

浏览器弹出来,但一直转圈加载,无法完成授权。

我的 VPN 开着,节点在美国洛杉矶。虽然已经在 VPN 里把 trae.cn 设为了直连,但似乎还是有问题。

我的 VPN 开着,节点在美国洛杉矶。虽然已经在 VPN 里把 trae.cn 设为了直连,但似乎还是有问题。

后来我只是再次点击“个人用户”,重新打开了浏览器登录页面。试了大概三次,突然就好了。浏览器正常弹出授权页面,扫码登录成功(如图3、图4)。

如图3:正在启动 Trae

如图3:正在启动 Trae


如图4:登录界面,个人用户登录

如图4:登录界面,个人用户登录

原因我也没深究,可能第一次请求被网络卡住了,多试几次就通了。

进入 IDE 模式,准备开工

登录后,界面底部可以选择模式:IDESOLO。我平时习惯自己写代码,只是把 AI 当作强力补全和问答助手,所以先用 IDE 模式(如图5)。

如图6:SOLO 模式欢迎界面,我切换到了 IDE

如图6:SOLO 模式欢迎界面,我切换到了 IDE

接下来就是写代码实现具体功能了。等我把整个工具写完并跑通,再写一篇分享具体的代码实现和踩坑点。

小结

折腾了一圈,终于把开发环境搞定了。回顾一下:

  1. 百度 API 主动推送是 sitemap 一直不可用时的替代方案。
  2. 对于中小项目,MySQL 记录已提交 URL + 按顺序增量扫描,完全够用,没必要上 Redis。
  3. AI 编程工具国内版的选择越来越丰富,Trae 国内版免费且支持国产模型,对个人开发者很友好。
  4. 安装时注意系统架构(amd64 vs arm64),以及登录时如果卡住,多点击几次“个人用户”重新触发浏览器授权往往能解决。

大家如果也在用 Trae 或者其他 AI IDE,欢迎交流~

]]>
https://www.shuijingwanwq.com/2026/06/01/15341/feed/ 0
配置开发环境:使用 Docker 管理多版本 MySQL 与 Redis https://www.shuijingwanwq.com/2026/06/01/15331/ https://www.shuijingwanwq.com/2026/06/01/15331/#respond Mon, 01 Jun 2026 08:48:33 +0000 https://www.shuijingwanwq.com/?p=15331 Post Views: 43

本文是「从 Windows 到 Ubuntu:迁移与配置完全指南」系列的开发环境篇。
适合已经完成 Ubuntu 系统安装,希望搭建高效、隔离、多版本数据库环境的开发者。


为什么要用 Docker 管理数据库?

在 Windows 下我习惯用 WSL2 + Docker 跑独立的数据库容器,迁移到 Ubuntu 后自然延续这一方式。相比直接在宿主机安装:

  • 多版本共存:同一台机器同时运行 MySQL 5.7 和 8.0,Redis 任意版本,互不干扰。
  • 环境隔离:每个项目的数据库独立或按版本共享,配置不打架。
  • 轻量易清理:容器停止即释放资源,数据卷保留,删除无残留。
  • 版本锁死mysql:5.7 镜像永远对应 5.7 最新版,不会因系统升级自动变更。

本文以 MySQL 5.7、MySQL 8.0、Redis 7.2 为例,搭建三个独立容器,供后续 Go/PHP 项目共用。


一、安装 Docker 引擎(纯命令行,不用 Docker Desktop)

Ubuntu 的“软件”或“应用中心”无法搜到 Docker Desktop,这是 Linux 发行版软件分发的特点——专业工具通常需要手动添加官方软件源。官方源安装更轻量、稳定。

1. 卸载可能冲突的旧包(全新系统可跳过)

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
  sudo apt remove -y $pkg
done

如果你是全新安装的 Ubuntu 26.04,这些包本来就不存在,此步骤可跳过。

2. 添加 Docker 官方 GPG 密钥和软件源

sudo apt update
sudo apt install -y ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl --http1.1 -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update

踩坑记录:如果 curl 下载 GPG 时报 curl: (16) Error in the HTTP2 framing layer,加上参数 --http1.1 即可解决。

3. 安装 Docker 引擎及 Compose 插件

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

4. 将当前用户加入 docker 组(免 sudo 执行)

sudo usermod -aG docker $USER

必须注销并重新登录(或重启),否则 docker 命令仍需要 sudo。

5. 验证安装

docker --version          # 输出如 Docker version 29.5.2
docker compose version    # 输出 Docker Compose version v2.x

二、网络与镜像拉取问题排查(真实踩坑实录)

在 VPN 环境下,拉取第一个测试镜像时就遇到了网络超时。以下是完整的排查和解决过程。

2.1 第一次尝试:直接运行 hello-world

docker run hello-world

输出:

Unable to find image 'hello-world:latest' locally
docker: Error response from daemon: failed to resolve reference "docker.io/library/hello-world:latest": failed to do request: Head "https://registry-1.docker.io/v2/library/hello-world/manifests/latest": dial tcp 128.242.240.85:443: i/o timeout

2.2 重启 Docker 服务,再次尝试

sudo systemctl restart docker
docker run hello-world

仍然超时,这次 IP 变成了 31.13.69.245

dial tcp 31.13.69.245:443: i/o timeout

2.3 检查宿主机能否访问 Docker Hub

curl -I https://registry-1.docker.io/v2/

输出:

HTTP/1.1 200 Connection established

HTTP/2 401 
date: Sun, 31 May 2026 10:04:04 GMT
content-type: application/json
content-length: 87
...

这说明宿主机网络正常(返回 401 是因为没有认证,但连接已建立),问题出在 Docker 守护进程自身。

2.4 配置国内镜像加速器(第一次尝试)

创建 /etc/docker/daemon.json,使用中科大、网易、百度镜像:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}
EOF
sudo systemctl daemon-reload
docker run hello-world

结果依然超时(IP 变为 103.97.3.19):

dial tcp 103.97.3.19:443: i/o timeout

2.5 再次重启 Docker,问题依旧

sudo systemctl restart docker
docker run hello-world

这次错误变了:

dial tcp: lookup docker.mirrors.ustc.edu.cn on 127.0.0.53:53: no such host

说明镜像加速器的域名无法解析 —— VPN 影响了 DNS。

2.6 最终方案:使用 DaoCloud 镜像 + 指定公共 DNS

修改 /etc/docker/daemon.json,同时配置镜像和 DNS:

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://docker.m.daocloud.io"],
  "dns": ["8.8.8.8", "1.1.1.1"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker run hello-world

这次终于成功拉取并运行:

latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete 
d5e71e642bf5: Download complete 
Digest: sha256:0e760fdfbc48ba8041e7c6db999bb40bfca508b4be580ac75d32c4e29d202ce1
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
...

结论:在 VPN 环境下,Docker 守护进程可能无法正确使用系统 DNS,需要显式指定公共 DNS(如 8.8.8.8)并配合一个可用的镜像加速器。DaoCloud 镜像 docker.m.daocloud.io 在此环境中可用。


三、部署 MySQL 5.7 / 8.0 和 Redis 7.2 容器

3.1 创建项目目录

mkdir -p ~/docker/services
cd ~/docker/services

3.2 编写 docker-compose.yml

cat > docker-compose.yml << 'EOF'
version: "3.8"      # 新版 Docker Compose 可省略该行,会有警告但无影响

networks:
  dev-network:
    driver: bridge

services:
  mysql57:
    image: mysql:5.7
    container_name: mysql57
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: mystrongpwd57
    ports:
      - "3306:3306"
    volumes:
      - ./data/mysql57:/var/lib/mysql
    networks:
      - dev-network

  mysql80:
    image: mysql:8.0
    container_name: mysql80
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: mystrongpwd80
    ports:
      - "3307:3306"
    volumes:
      - ./data/mysql80:/var/lib/mysql
    networks:
      - dev-network
    command: --default-authentication-plugin=mysql_native_password

  redis:
    image: redis:7.2
    container_name: redis72
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - ./data/redis72:/data
    command: redis-server --requirepass myredispass --appendonly yes
    networks:
      - dev-network
EOF

避坑提醒:MySQL 8.0 需要添加 --default-authentication-plugin=mysql_native_password,否则某些旧客户端连接会报 caching_sha2_password 错误。

3.3 第一次启动(遇到网络波动)

docker compose up -d

输出显示:

WARN[0000] the attribute `version` is obsolete...
[+] up 19/24
 ✘ Image mysql:8.0  Error failed to resolve reference ... EOF

错误原因是拉取 MySQL 8.0 时认证失败(EOF),可能是镜像加速器暂时性问题。

3.4 直接重试,成功

docker compose up -d

这次所有镜像都拉取成功:

[+] up 25/34
 ✔ Image mysql:5.7   Pulled
 ✔ Image redis:7.2   Pulled
 ✔ Image mysql:8.0   Pulled
 ✔ Network services_dev-network Created
 ✔ Container mysql57 Started
 ✔ Container redis72 Started
 ✔ Container mysql80 Started

经验:Docker Hub 或镜像加速器偶发性网络问题,重试一次往往就能解决。


四、验证容器运行及连接测试

4.1 查看容器状态

docker ps

输出:

CONTAINER ID   IMAGE       COMMAND                   STATUS          PORTS                                                    NAMES
9ec38ca384b6   redis:7.2   "docker-entrypoint.s…"   45 seconds ago   Up 42 seconds   0.0.0.0:6379->6379/tcp, [::]:6379->6379/tcp              redis72
4e93fd2e2c3f   mysql:8.0   "docker-entrypoint.s…"   45 seconds ago   Up 42 seconds   33060/tcp, 0.0.0.0:3307->3306/tcp, [::]:3307->3306/tcp   mysql80
29598dd51087   mysql:5.7   "docker-entrypoint.s…"   45 seconds ago   Up 42 seconds   0.0.0.0:3306->3306/tcp, [::]:3306->3306/tcp, 33060/tcp   mysql57

4.2 连接 MySQL 5.7

docker exec -it mysql57 mysql -uroot -pmystrongpwd57

进入 MySQL 命令行,执行 exit 退出。

4.3 连接 MySQL 8.0

docker exec -it mysql80 mysql -uroot -pmystrongpwd80

同样成功进入。

4.4 连接 Redis

docker exec -it redis72 redis-cli -a myredispass

输出提示 Warning: Using a password with '-a' ... may not be safe(仅安全警告,不影响使用),然后进入 127.0.0.1:6379> 提示符。

至此,三个数据库容器均已正常运行。

至此,三个数据库容器均已正常运行。

五、供项目容器连接(网络互通)

数据库容器位于自定义网络 services_dev-network(由 docker-compose 自动创建)。项目容器只需加入同一外部网络,即可通过 容器名 访问。

示例 docker-compose.yml 片段:

networks:
  default:
    external:
      name: services_dev-network

services:
  go-app:
    image: golang:1.26-alpine
    networks:
      - default
    environment:
      DB_HOST: mysql57         # 或 mysql80
      REDIS_HOST: redis72

代码中连接 mysql57:3306redis72:6379 即可。


六、版本选择说明与注意事项

服务版本主流程度生命周期提醒
MySQL 5.75.7.44占约 18.8%,属于遗留系统常用版本已于 2023 年 10 月 EOL,不再有官方安全更新,非必要不建议新项目使用
MySQL 8.08.0.46当前最主流(占比 58%)官方支持将于 2026 年 4 月 30 日 结束,后续需升级到 8.4 LTS
Redis 7.27.2.x稳定且成熟开源分支 Valkey 的基础版本,可放心使用

若为新项目,推荐直接使用 MySQL 8.4 LTS(支持至 2031 年)和 Redis 7.2


七、日常管理命令速查

操作命令
启动所有服务cd ~/docker/services && docker compose up -d
停止所有服务cd ~/docker/services && docker compose down
单独重启 MySQL 8.0docker restart mysql80
查看 MySQL 5.7 日志docker logs -f mysql57
进入 Redis 命令行docker exec -it redis72 redis-cli -a 密码
清理未使用的镜像/容器docker system prune -a
备份数据卷tar -czf mysql57-backup.tar.gz ~/docker/services/data/mysql57

八、总结

至此,我们完成了一套干净、可复用的数据库容器环境:

  • MySQL 5.7 → 端口 3306
  • MySQL 8.0 → 端口 3307
  • Redis 7.2 → 端口 6379

所有数据持久化在 ~/docker/services/data/,容器随系统启动自动运行。后续任何 Go、PHP、Node.js 项目,只需通过 Docker 网络连接这些容器名,即可快速获得多版本数据库支持。


本文命令均在 Ubuntu 26.04 下验证通过,Docker 版本 29.5.2。
如有疑问或交流,欢迎在评论区留言。

]]>
https://www.shuijingwanwq.com/2026/06/01/15331/feed/ 0
在 MySQL 8.0 中,in 条件中的参数数量有 10000 个,导致查询时长过长,接口响应超时的分析 https://www.shuijingwanwq.com/2026/04/11/9468/ https://www.shuijingwanwq.com/2026/04/11/9468/#respond Sat, 11 Apr 2026 03:40:43 +0000 https://www.shuijingwanwq.com/?p=9468 Post Views: 154

1、in 条件中的参数数量有 10000 个(产品需要上限是 10000 个),导致查询时长过长,接口响应超时。如图1

in 条件中的参数数量有 10000 个(产品需要上限是 10000 个),导致查询时长过长,接口响应超时


{
  "filter": {
    "plat_order_id": [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      ...,
      10000
    ]
  },
  "page": 1,
  "per_page": 100
}




{
    "status_code": 500,
    "code": 0,
    "message": "Maximum execution time of 300 seconds exceeded",
    "trace": {
        "line": 420,
        "file": "E:\\wwwroot\\erp-backend\\vendor\\laravel\\framework\\src\\Illuminate\\Database\\Connection.php",
        "class": "Symfony\\Component\\ErrorHandler\\Error\\FatalError",
        "trace": [
            "#0 {main}"
        ]
    }
}


2、当将 plat_order_id 的参数数量从 10000 个减少至 5 个后,接口响应不再超时,表中的记录总数为 2000 万条。如图2

当将 plat_order_id 的参数数量从  10000 个减少至 5 个后,接口响应不再超时,表中的记录总数为 2000 万条。

3、生成 SQL 如下



select
  count(*) as aggregate
from
  `orders`
where
  `orders`.`plat_order_id` in ('95798');


4、调整 SQL,决定强制使用索引 USE INDEX,不再超时



select
  count(*) as aggregate
from
  `orders` USE INDEX(`orders_plat_order_id_index`)
where
  `orders`.`plat_order_id` in ('95798')


]]>
https://www.shuijingwanwq.com/2026/04/11/9468/feed/ 0
告别折磨人的 MySQL 分库分表!亿级数据无感扩容,后端需要懂的 NewSQL 选型 https://www.shuijingwanwq.com/2026/03/27/9389/ https://www.shuijingwanwq.com/2026/03/27/9389/#respond Fri, 27 Mar 2026 08:53:10 +0000 https://www.shuijingwanwq.com/?p=9389 Post Views: 149

最近面试交流中,面试官问到了我对 TiDB 落地实践 的掌握深度。

从业 14 年 PHP 后端一线研发,我长期深耕 MySQL 高并发、大数据表架构演进与性能优化,从单机调优、主从复制,到 Sharding-JDBC / MyCat 手工分库分表整套方案都深度落地过,传统 MySQL 扩容痛点、跨分片难题、事务一致性、运维代价,一路亲身踩坑打磨。

日常工作里虽然一直在关注 TiDB 这类主流 NewSQL 分布式方案、也在线上架构评审和技术调研中多次接触,但一直没有专门系统化整理成标准选型框架 + 落地边界文档。借着这次面试契机,我结合多年 MySQL 大规模实战经验、业界成熟生产案例,再对照官方架构与最佳实践,完整复盘沉淀出这一份:

告别手工分库分表、亿级数据无感扩容的全维度选型参考,同时厘清工程里长期争论的经典问题:

业务上规模化使用 TiDB 之后,ElasticSearch 应该如何分层取舍、各司其职,哪些场景保留、哪些场景可以精简,彻底讲清 RDBMS 与检索引擎的真实架构边界。

整篇内容基于资深 PHP+MySQL 实战视角总结,兼顾面试应答标准 + 企业生产真实落地依据,既是知识体系补全,也方便同行直接用作数据库架构选型参考。

作为一名拥有多年开发经验的老程序员,这些年我踩过最多的坑,莫过于MySQL数据库的扩容难题。从最开始的单机MySQL扛业务,到单表数据突破千万后出现的查询卡顿、写入抖动,再到不得已上手Sharding-JDBC、MyCat做手工分库分表,整个过程堪称“运维噩梦”——自己规划分片键、调试路由规则,跨分片JOIN、跨分片事务处处是坑,扩容时还要手动迁移数据、修改业务代码,PHP业务层还要兼容复杂的分页、排序和聚合逻辑,中小团队根本扛不住这种架构的重量。

相信很多和我一样的后端,都有过同一个灵魂拷问:有没有一种数据库,能兼容MySQL协议、PHP无缝对接,不用我们关心分库分表,就能轻松支撑亿级、十亿级数据量,还能保证高可用和强一致性?答案是肯定的——NewSQL分布式数据库,就是为解决这个痛点而生的。今天这篇文章,我就结合自己的开发经验,详细盘点主流的免手工分片类MySQL分布式数据库(含开源+公有云),同时解答大家最关心的问题:用上TiDB后,ElasticSearch(ES)还需要吗?为后续生产环境数据库选型提供最实用的参考。

一、先复盘:MySQL手工分库分表的那些“致命痛点”

在聊NewSQL之前,我们先好好梳理一下传统MySQL架构的演进瓶颈,也让大家明白,为什么我们迫切需要“免分片”的分布式数据库。

1. 单机MySQL的天然局限:单表数据量一旦突破千万,就会出现明显的性能瓶颈——索引臃肿导致查询变慢,写入并发升高后出现抖动,扩容只能靠垂直升级服务器(加CPU、加内存、加磁盘),但这种方式有明确上限,撑不起亿级数据和高并发场景。

  • 分片规则全靠人工规划:选什么字段作为分片键、如何拆分数据、路由规则怎么配置,都需要自己反复调试,一旦分片键选得不合理,后续扩容和维护会更麻烦;
  • 跨分片操作极难实现:跨分片JOIN、跨分片事务几乎是“玄学”,要么放弃强一致性,要么写复杂的业务逻辑兜底,很容易出现数据不一致的问题;
  • 扩容迁移痛苦不堪:当数据继续增长,需要新增分片时,要手动迁移数据、调整路由配置,甚至修改业务代码,期间很容易出现服务中断或数据丢失;
  • 运维成本翻倍:需要专门安排人员维护中间件和分片集群,监控各个分片的负载、排查分片异常,中小团队根本没有足够的人力支撑。

对于我们后端来说,最痛苦的是:原本熟悉的MySQL语法,在分库分表后需要做大量适配,Laravel、Yii2等框架的ORM用法也要调整,业务开发效率大幅下降。而NewSQL的核心价值,就是帮我们摆脱这些麻烦——兼容MySQL协议、自动分片、免手工维护,业务层完全无感,亿级数据轻松扛住

二、主流免手工分片 类MySQL分布式数据库全盘点(开源+公有云,生产可用)

目前市面上的免手工分片、兼容MySQL的分布式数据库,主要分为两大类别:开源自研版(适合私有化部署、自建机房)和公有云托管版(适合不想运维、开箱即用的企业)。下面我们逐一拆解,重点分析每个数据库的核心亮点、适合场景和短板,方便大家根据自己的业务情况选型。

(一)开源自研版(私有化部署/自建机房首选)

这类数据库可以部署在自己的服务器上,自主掌控数据和运维,适合对数据安全性要求高、有自建机房的企业,也是我们PHP开发者自学、小团队落地的首选。

① TiDB(PingCAP)【重点推荐,最适配PHP生态】

这也是我面试时被问到的数据库,也是目前最适合PHP后端、最容易落地的NewSQL数据库。作为PingCAP自研的开源分布式NewSQL数据库,它的核心定位就是“替代MySQL分库分表,兼容MySQL生态,支撑亿级数据高并发”。

核心亮点(重点关注,贴合PHP开发场景):

  • MySQL兼容性拉满:百分百兼容MySQL 5.7协议、语法和连接驱动,PHP项目(Laravel、Yii2、ThinkPHP)可以直接连接,不需要修改任何SQL语句和业务代码,平滑迁移毫无压力;
  • 自动分片,彻底解放双手:底层会自动将数据按“Region”(默认96MB)分片,数据均匀打散到多个TiKV节点,不需要人工规划分片键、不需要手动迁移数据,数据涨到亿级、十亿级,只需要新增TiKV节点即可,扩容完全无感;
  • 高可用+强事务:基于Raft协议实现3副本部署,单节点故障后自动切换,数据零丢失、服务不中断,支持跨机房容灾;同时支持分布式强事务ACID,解决了分库分表后跨片事务难的痛点,适合订单、支付等核心业务;
  • HTAP一体,兼顾交易和分析:TiKV(行存引擎)负责OLTP(在线事务,如用户下单、支付),TiFlash(列存引擎,可选)负责OLAP(在线分析,如后台报表、数据统计),数据实时同步,不需要再搭建单独的数据仓库做ETL,一套数据库搞定交易+分析;
  • 存算分离,弹性扩容:TiDB Server(计算层)和TiKV(存储层)分离,想提升并发能力,就新增TiDB节点;想存储更多数据,就新增TiKV节点,扩容灵活,成本可控。

适合场景:

  • 互联网场景:订单、用户、账单、流水等大表,数据量从千万级到百亿级,需要高并发读写;
  • PHP老项目迁移:不想修改业务代码,想摆脱分库分表的麻烦,平滑替代传统MySQL;
  • 中小团队落地:学习成本低,本地一条命令就能启动测试集群,运维难度远低于分库分表+中间件的方案。

短板:

  • 小规模数据部署略重:相对于单机MySQL,TiDB需要部署TiDB、TiKV、PD三个核心组件,服务器资源占用略高,适合数据量较大的场景,小数据量场景(单表百万级以下)用单机MySQL更高效;
  • 极低延迟查询不如原生MySQL:对于简单的、微秒级延迟要求的查询(如简单的主键查询),TiDB的延迟略高于单机MySQL,但对于绝大多数PHP业务(如Web接口、后台管理),完全满足需求。

② OceanBase 开源版(蚂蚁集团自研)

OceanBase是蚂蚁集团自研的分布式数据库,核心优势是“金融级稳定性”,支付宝的核心账务系统、双十一峰值都是靠它支撑的,开源版可以免费使用,适合对稳定性要求极高的场景。

核心亮点:

  • 金融级高可用:经过支付宝多年实战验证,支持异地多活、故障自动切换,数据零丢失,满足金融行业的合规要求;
  • 兼容性强:兼容MySQL协议,同时支持部分Oracle语法,适合需要从Oracle迁移到MySQL生态的企业;
  • 资源利用率高:支持自动分区、基线合并、数据高压缩,相同数据量下,存储成本低于TiDB,服务器资源利用率更高;
  • 强一致性:分布式事务支持ACID,适合账务、支付等核心金融场景。

适合场景:金融、支付、核心账务系统,对数据一致性、稳定性要求极高的企业。

短板:

  • 学习曲线陡:架构比TiDB更复杂,运维难度高,需要专门的运维人员维护,中小团队落地成本高;
  • 社区生态不如TiDB:国内社区活跃度低于TiDB,遇到问题时,文档和解决方案不如TiDB丰富;
  • PHP生态适配不如TiDB:虽然兼容MySQL协议,但在PHP框架的适配、社区案例上,比TiDB少,落地时可能需要额外调试。

③ PolarDB-X 开源版(阿里自研)

PolarDB-X是阿里原生的分布式MySQL数据库,定位和TiDB类似,核心优势是“阿里生态适配”,适合阿里系技术栈的企业,开源版可以私有化部署。

核心亮点:

  • MySQL兼容度高:无缝兼容MySQL语法、协议,PHP项目可以直接连接,迁移成本低;
  • 自动分片+全局二级索引:底层自动分片,支持全局二级索引,解决了分片后索引查询的痛点;
  • 阿里生态联动:和阿里云的其他产品(如RDS、OSS、DataWorks)联动性强,适合已经使用阿里生态的企业。

适合场景:阿里生态重度用户,需要私有化部署、兼容MySQL的分布式数据库。

短板:社区活跃度低于TiDB,开源版的功能更新速度不如TiDB,非阿里生态的企业落地优势不明显。

(二)公有云托管版(不用运维、开箱即用,企业生产首选)

对于大多数企业(尤其是中小团队)来说,自建分布式数据库集群的运维成本太高,此时选择公有云托管版,是更高效、更省心的选择——厂商负责底层的部署、运维、扩容、故障处理,我们只需要专注于业务开发,开箱即用。

1. 阿里云 PolarDB

阿里云的分布式MySQL托管版,基于PolarDB-X开源版优化而来,是阿里云推荐的“亿级数据分布式解决方案”,也是国内使用最广泛的公有云分布式MySQL之一。

核心优势:云端托管,底层分片细节完全屏蔽,业务层完全无感;支持弹性扩容、自动备份、故障自愈;和阿里云RDS、ECS、SLS等产品无缝联动,适合阿里云上的PHP项目落地;秒杀、订单、海量流水等场景有成熟的解决方案。

2. 腾讯云 TDSQL MySQL版

腾讯云自主研发的分布式MySQL,核心优势是“金融级稳定性”和“传统项目适配”,在金融、政企领域落地较多。

核心优势:自动分片、分布式强事务、异地多活;兼容MySQL协议,PHP项目迁移成本低;提供完善的监控、备份、运维工具,适合不想投入太多运维人力的企业;支持国产化适配,适合政企项目。

3. 华为云 GaussDB(for MySQL)

华为云的分布式MySQL,核心优势是“存算分离”和“超大规模数据支撑”,适合数据量极大、并发极高的场景。

核心优势:支持PB级数据存储,自动分片、弹性扩容;兼容MySQL协议,PHP无缝对接;提供金融级高可用,适合核心业务落地;华为云生态联动性强,适合使用华为云的企业。

4. AWS Aurora MySQL 分布式扩展

亚马逊云的分布式MySQL,核心优势是“海外部署”和“原生MySQL兼容”,适合海外业务、需要对接AWS生态的企业。

核心优势:完全兼容原生MySQL,PHP项目可以直接连接;支持弹性扩容,底层自动分片;海外节点覆盖广,适合海外部署的业务;提供完善的监控和运维工具。

(三)生产选型对比表(直接对照,快速决策)

为了方便大家快速选型,我整理了一张对比表,涵盖核心维度,生产环境直接参考即可:

数据库归属形态MySQL兼容度自动分片分布式事务运维难度典型最佳场景
TiDB(开源)开源自建极高(100%兼容5.7)全自动强ACID中等(适合中小团队)互联网/PHP项目迁移、替代分库分表、亿级结构化数据
OceanBase(开源)开源自建高(兼容MySQL+部分Oracle)全自动金融级强一致高(适合专业运维)账务/支付/银行核心、金融级高可用场景
PolarDB-X(开源)开源自建极高全自动中等阿里生态自建、兼容MySQL的分布式场景
阿里云PolarDB公有云托管极高厂商屏蔽细节极低(厂商运维)阿里云体系、不想运维、亿级数据场景
腾讯云TDSQL MySQL版公有云托管极高厂商屏蔽细节极低腾讯云体系、金融/政企项目、传统项目迁移
MyCat+MySQL(传统方案)中间件组合依赖分片规则人工规划弱、坑多极高老旧历史项目无奈续命,新项目不推荐

一句话选型结论(重点记,面试+生产都能用):

  • PHP传统Web/互联网业务、想低成本迁移、自学落地、面试加分 → TiDB(开源版)最优;
  • 金融账务、支付等核心场景,追求极致稳定性 → OceanBase(开源版)或腾讯云TDSQL;
  • 全阿里云体系、不想投入运维人力、直接云上落地 → 阿里云PolarDB;
  • 海外业务、需要对接AWS生态 → AWS Aurora MySQL;
  • 老旧历史项目,无法直接迁移NewSQL → 暂时用MyCat+MySQL续命,新项目优先选NewSQL。

三、核心答疑:用上TiDB后,ElasticSearch(ES)能不用吗?

这是我在考虑TiDB时,最纠结的一个问题——很多PHP项目中,我们都会用MySQL存储业务数据,用ES做全文检索(如文章搜索、商品搜索),如果用上了TiDB,能不能把ES干掉,减少一套集群的运维成本?经过深入了解和实践验证,答案很明确:TiDB不能完全替代ES,两者定位不同,是否需要保留ES,取决于你的业务是否需要全文检索

(一)先明确:TiDB和ES的核心定位,完全不一样

我们先理清两者的核心强项,就知道为什么不能互相替代了。

1. ElasticSearch(ES)的核心强项:全文检索+非结构化数据处理

ES的本质是“检索引擎”,不是数据库,它的核心价值的是“快速检索”,尤其是全文分词检索,这是TiDB无法替代的:

  • 全文分词搜索:支持中文、英文等多种语言的分词,比如“PHP分布式数据库”,可以拆分成“PHP”“分布式”“数据库”三个关键词,用户搜索任意一个关键词,都能快速匹配到相关内容,还能实现高亮、权重排序(比如匹配度高的内容排在前面);
  • 日志检索:系统日志、操作日志、错误日志等非结构化/半结构化数据,ES能快速索引、检索,方便我们排查问题(比如根据关键词快速定位某条错误日志);
  • 多维自由筛选:支持多字段、多条件的自由筛选,比如商品搜索时,可同时根据价格、分类、销量、评价等多个维度筛选,且检索速度极快;
  • 非结构化数据处理:支持文本、图片(需插件)等非结构化数据的检索,这是TiDB完全不具备的能力。

2. TiDB的核心强项:结构化事务数据+标准SQL操作

TiDB的本质是“分布式关系型数据库”,核心价值是“存储和管理结构化事务数据”,替代的是MySQL,而不是ES:

  • 结构化数据存储:适合存储订单、用户、余额、流水等结构化数据,保证数据的强一致性和事务性;
  • 标准SQL操作:支持JOIN、事务、DDL、分页、排序等所有MySQL支持的SQL操作,PHP业务层可以直接用熟悉的方式操作;
  • 亿级数据支撑:自动分片,轻松支撑亿级、十亿级结构化数据的存储和查询;
  • 基础分析能力:通过TiFlash列存引擎,可以做简单的大数据统计分析(如月度订单量、用户活跃度统计),但无法实现ES那样的全文检索。

(二)生产落地结论:分场景判断,不用盲目保留或删除ES

结合PHP项目的常见场景,我们可以分两种情况判断,避免浪费资源,也避免踩坑:

✅ 可以下线/不用部署ES的场景(TiDB完全够用)

如果你的业务只有“结构化数据查询”,没有全文检索需求,那么ES完全可以不用部署,TiDB+TiFlash就能搞定所有需求:

  • 用户基础信息查询:根据用户ID、手机号、用户名等精准条件查询,TiDB的索引查询速度完全满足;
  • 订单、账单查询:根据订单号、用户ID、时间范围等条件查询,支持分页、排序、简单聚合(如统计某用户的订单总数);
  • 后台普通报表:如销售报表、用户报表,通过TiFlash列存引擎,能快速完成统计分析,不需要再同步数据到ES或数据仓库;
  • 精准条件查询:所有不需要中文分词、不需要模糊检索的场景,TiDB都能胜任,且性能优于ES(因为ES的检索需要经过分词、倒排索引等过程,延迟高于TiDB的精准查询)。

❌ 绝对不能替换ES的场景(必须保留)

只要你的业务有以下需求,ES就不能省,TiDB无法替代:

  • 全文分词搜索:如博客文章、商品标题/详情、新闻内容的模糊搜索、关键词检索,需要分词、高亮、权重排序;
  • 日志检索:系统日志、操作日志、错误日志的集中检索、排查,尤其是海量日志场景(如每天千万级日志);
  • 多维自由筛选:如电商商品搜索,用户可同时根据价格、分类、销量、评价、品牌等多个维度自由筛选,且要求检索速度快;
  • 非结构化文本检索:如用户评论、文章内容等非结构化文本的检索、分析。

(三)折中方案:中小团队省钱技巧(轻度搜索不用ES)

如果你的业务只有“轻度搜索”需求(比如简单的关键词模糊查询,不需要复杂分词和权重排序),不想维护笨重的ES集群,可以考虑以下折中方案,比ES更轻量、更易维护:

  • TiDB + MySQL原生全文索引:TiDB兼容MySQL的全文索引功能,对于简单的中文分词、模糊查询(如用户名、标题模糊搜索),可以凑合使用,但性能和功能远不如ES,适合轻度需求;
  • 接入轻量检索引擎:如Meilisearch、Typesense,这些检索引擎比ES轻量很多,部署简单、运维成本低,支持基本的分词、高亮、模糊搜索,适合中小团队的轻度检索需求;
  • 注意:如果是重度搜索需求(如电商商品搜索、海量文章检索),还是老老实实保留ES,避免影响用户体验。

四、结合14年PHP经验:架构演进最佳路线(生产落地实战)

结合我这些年的PHP开发经验,给大家推荐一套“极简、高效、易落地”的架构演进路线,适合大多数PHP项目,尤其是从传统MySQL分库分表迁移的项目:

(一)老架构(痛苦版,不推荐继续使用)

PHP + 单机MySQL → 数据爆涨 → 主从复制瓶颈 → MyCat/Sharding-JDBC手工分库分表 → 改代码、改SQL、跨片坑无数、运维成本爆炸

这套架构的问题的是:运维复杂、业务开发效率低、扩容困难,适合老旧历史项目暂时续命,新项目绝对不要用。

(二)新极简架构(推荐,生产落地首选)

根据业务是否有检索需求,分为两种方案,都能彻底摆脱手工分库分表的麻烦:

方案1:无检索需求(如后台管理系统、支付系统)

PHP + TiDB → 搞定所有需求

  • 不用关心分库分表,数据涨到亿级也能平稳运行;
  • PHP代码、SQL语句完全不用改,平滑迁移;
  • TiFlash负责后台报表统计,不需要额外搭建数据仓库;
  • 运维简单,只需要维护TiDB集群,比分库分表+中间件省心10倍。

方案2:有检索需求(如电商、博客、内容平台)

PHP + TiDB(核心业务数据) + 轻量检索引擎/ES(检索需求)

  • TiDB:存储订单、用户、余额等核心结构化数据,保证事务和一致性;
  • 轻量检索引擎(Meilisearch):处理轻度检索需求(如简单关键词搜索),运维成本低;
  • ES(可选):处理重度检索需求(如商品全文搜索、日志检索),只部署必要的节点,减少运维成本;
  • 数据同步:通过TiDB的binlog,将需要检索的数据同步到检索引擎,实现数据实时更新。

这套架构的优势:核心业务稳定、检索需求满足、运维成本可控,完美适配PHP生态,中小团队也能轻松落地。

五、总结:PHP后端数据库选型的终极思路

作为一名14年的PHP老程序员,经历过MySQL分库分表的折磨,也深入了解了NewSQL的优势后,我对数据库选型有了更清晰的认知,在这里分享给大家,也作为这篇博客的收尾:

1. 对于PHP项目来说,TiDB是目前最适合的开源NewSQL数据库——兼容MySQL、自动分片、免手工维护、PHP零改造,能彻底解决分库分表的痛点,支撑亿级数据高并发,无论是自学、面试还是生产落地,都是最优解;

2. 公有云托管版(如阿里云PolarDB、腾讯云TDSQL),适合不想投入运维人力的企业,开箱即用,稳定性有保障,是企业生产的首选;

3. TiDB和ES不是“二选一”的关系,而是“互补”的关系——TiDB解决结构化数据的存储、事务和基础分析,ES解决全文检索和非结构化数据处理,是否需要保留ES,取决于业务的检索需求;

4. 新项目尽量避免手工分库分表,优先选择NewSQL数据库,减少架构复杂度和运维成本;老旧项目可以逐步迁移,先将核心大表迁移到TiDB,再逐步淘汰分库分表中间件;

5. 数据库选型的核心原则:贴合业务需求、降低开发和运维成本、保证稳定性和可扩展性,不用盲目追求“高大上”,适合自己业务的才是最好的。

最后,希望这篇文章能帮到和我一样,被MySQL分库分表折磨过的后端开发者,也希望大家能快速掌握NewSQL的核心知识点,在面试和生产选型中少走弯路,彻底告别手工分库分表的痛苦!

]]>
https://www.shuijingwanwq.com/2026/03/27/9389/feed/ 0
MySQL 报错 Can’t connect to /var/lib/mysql/mysql.sock (2) 排查全过程 https://www.shuijingwanwq.com/2026/03/23/9368/ https://www.shuijingwanwq.com/2026/03/23/9368/#respond Mon, 23 Mar 2026 07:12:47 +0000 https://www.shuijingwanwq.com/?p=9368 Post Views: 130

1、打开网站后台网址,报错:Unable to connect to database: Can’t connect to local MySQL server through socket ‘/var/lib/mysql/mysql.sock’ (2)

2、修复 MySQL 连接报错问题,查看 ISC 框架(Interspire Shopping Cart,老牌电商系统)的配置文件,确认连接的是本机的数据库。如图1

修复 MySQL 连接报错问题,查看 ISC 框架(Interspire Shopping Cart,老牌电商系统)的配置文件,确认连接的是本机的数据库。

3、然后检查 MySQL 状态(确认是否启动成功),发现已经停止。启动 MySQL 失败



[root@li1269-60 jfc]# service mysqld status                                     |
mysqld is stopped
[root@li1269-60 jfc]# service mysqld start

Timeout error occurred trying to start MySQL Daemon.
Starting mysqld:  [FAILED]
[root@li1269-60 jfc]#



4、查看 MySQL 启动失败的关键日志(最核心的排查命令),从你给出的 MySQL 日志片段来看,核心错误是 Table ‘./jammerfromchina/cart_prodd’ 损坏—— 这是导致 MySQL 启动超时 / 失败的根本原因(数据表损坏会让 InnoDB/MyISAM 引擎加载失败,MySQL 启动流程卡住)。



[root@li1269-60 jfc]# grep -i "error|fail|timeout" /var/log/mysqld.log
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromchina/cart_prodd
130419  7:01:24 [ERROR] /usr/libexec/mysqld: Table './jammerfromc^C[ 3502.924666



5、确认磁盘资源是否充足是排查当前问题的关键前置步骤(磁盘满是导致 MySQL 启动失败、数据表损坏的核心诱因之一)。最终确认:磁盘 100% 满(根分区 /dev/sda 78G 已用尽)。如图2

确认磁盘资源是否充足是排查当前问题的关键前置步骤(磁盘满是导致 MySQL 启动失败、数据表损坏的核心诱因之一)。最终确认:磁盘 100% 满(根分区 /dev/sda 78G 已用尽)


[root@li1269-60 jfc]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda               78G   78G     0 100% /
tmpfs                 1.9G  116K  1.9G   1% /dev/shm



6、发现 Linode 实例可以调整大小了。发现当前 Linode 4 GB,存储是 80GB。可以调整为 Linode 8 GB ,存储是 160 GB。参考:Linode 实例升级配置失败的排查分析,升级配置为 160GB 配置

7、再次查看磁盘占用情况,确认符合预期



[root@li1269-60 ~]# df -h
Filesystem            Size  Used Avail Use% Mounted on
/dev/sda              148G   78G   68G  54% /
tmpfs                 3.9G  116K  3.9G   1% /dev/shm
[root@li1269-60 ~]#



8、现在打开后台,已经不再报错。可以正常登录。如图3

现在打开后台,已经不再报错。可以正常登录
]]>
https://www.shuijingwanwq.com/2026/03/23/9368/feed/ 0
在 Yii2 中,如何保证接口响应中的字段类型与 MySQL 中的一致? https://www.shuijingwanwq.com/2025/06/11/9120/ https://www.shuijingwanwq.com/2025/06/11/9120/#respond Wed, 11 Jun 2025 01:41:32 +0000 https://www.shuijingwanwq.com/?p=9120 Post Views: 76 1、查看现有的接口响应,”status”: “1”,,status 字段在表中的类型是 tinyint 。如图1
查看现有的接口响应,"status": "1",,status 字段在表中的类型是 tinyint

图1

2、配置 components[‘db’] 中调整,添加 attributes[PDO::ATTR_STRINGIFY_FETCHES] => false 与 attributes[PDO::ATTR_EMULATE_PREPARES] => false


return [
    'components' => [
        'db' => [
            'class' => 'yii\db\Connection',
            'dsn' => 'mysql:host=localhost;dbname=apply_server_v6',
            'username' => 'root',
            'password' => 'root',
            'charset' => 'utf8mb4',
            'attributes' => [
                PDO::ATTR_STRINGIFY_FETCHES => false,
                PDO::ATTR_EMULATE_PREPARES => false,
            ],
        ],
    ],
];


3、再次查看接口的响应,字段的类型已经与 MySQL 的保持一致。符合预期。如图2
再次查看接口的响应,字段的类型已经与 MySQL 的保持一致。符合预期

图2

]]>
https://www.shuijingwanwq.com/2025/06/11/9120/feed/ 0
yii\base\InvalidArgumentException: Malformed UTF-8 characters, possibly incorrectly encoded in C:\wwwroot\object\src\vendor\yiisoft\yii2\helpers\BaseJson.php:147 https://www.shuijingwanwq.com/2025/03/28/8945/ https://www.shuijingwanwq.com/2025/03/28/8945/#respond Fri, 28 Mar 2025 01:26:54 +0000 https://www.shuijingwanwq.com/?p=8945 Post Views: 161 1、查看接口中的响应数据,定位原因在于响应字段中的 checkin_location_coordinates 为 MySQL 的 point 类型。如图1
查看接口中的响应数据,定位原因在于响应字段中的 checkin_location_coordinates 为 MySQL 的 point 类型

图1

2、决定调整查询 SQL,使用 MySQL 的空间函数(如 ST_X 和 ST_Y)提取经纬度,避免直接操作 POINT 字段。通过 addSelect() 方法,可以在查询所有字段的基础上,额外添加经度和纬度字段。然后再 unset 掉 checkin_location_coordinates 字段。


SELECT * FROM `survey_forms` WHERE (`id`='1825203750690273') AND (`user_id`='1825203750689422') LIMIT 1




SELECT *, ST_X(checkin_location_coordinates) AS `checkin_location_longitude`, ST_Y(checkin_location_coordinates) AS `checkin_location_latitude` FROM `survey_forms` WHERE `id`='1825203750690273' LIMIT 1




$form = SurveyForm::find()
	->select('*') // 查询所有字段
	->addSelect([
		'ST_X(checkin_location_coordinates) AS checkin_location_longitude',
		'ST_Y(checkin_location_coordinates) AS checkin_location_latitude',
	])
	->where([
	'id' => $id,
])->limit(1)->asArray()->one();
unset($form['checkin_location_coordinates']);


3、但是,这种方案在所有的这个模型的查询中皆要调整。工作量超出预期。需要在模型文件中实现 自动添加查询 checkin_location_longitude、checkin_location_latitude。然后 unset(checkin_location_coordinates)。发现 未执行到 fields() 方法。最后仍然只有在控制器中 unset($form[‘checkin_location_coordinates’]);。决定仍然采用第二步骤的方案。


    public $checkin_location_longitude;
    public $checkin_location_latitude;

    public function afterFind()
    {
        parent::afterFind();
        // 设置经度和纬度
        $this->checkin_location_longitude = Yii::$app->db->createCommand("SELECT ST_X(checkin_location_coordinates) FROM {$this->tableName()} WHERE id = :id", [':id' => $this->id])->queryScalar();

        $this->checkin_location_latitude = Yii::$app->db->createCommand("SELECT ST_Y(checkin_location_coordinates) FROM {$this->tableName()} WHERE id = :id", [':id' => $this->id])->queryScalar();

        // 排除原始字段
        unset($this->checkin_location_coordinates);
    }

    public function fields()
    {
        $fields = parent::fields();
        Yii::debug('Fields before modification: ' . print_r($fields, true));
        // 添加自定义字段
        $fields['checkin_location_longitude'] = 'checkin_location_longitude';
        $fields['checkin_location_latitude'] = 'checkin_location_latitude';
        // 排除原始字段
        unset($fields['checkin_location_coordinates']);
        Yii::debug('Fields after modification: ' . print_r($fields, true));
        return $fields;
    }




$form = SurveyForm::find()->where([
            'id' => $id,
        ])->with(['fields', 'fields.diagram_image', 'fields.options', 'cover_image', 'submit_success_image'])->limit(1)->asArray()->one();

unset($form['checkin_location_coordinates']);


4、最终决定放弃使用 point 字段类型。调整为 checkin_location_longitude decimal(9,6) 、checkin_location_latitude decimal(9,6)]]>
https://www.shuijingwanwq.com/2025/03/28/8945/feed/ 0
在 Lavavel 9 中 验证 符合 MySQL 字段类型 decimal(8,2) 的字段 https://www.shuijingwanwq.com/2024/07/05/8699/ https://www.shuijingwanwq.com/2024/07/05/8699/#respond Fri, 05 Jul 2024 07:55:56 +0000 https://www.shuijingwanwq.com/?p=8699 Post Views: 62 1、在 Lavavel 9 中 验证 符合 MySQL 字段类型 decimal(8,2) 的字段。如图1
在 Lavavel 9 中 验证 符合 MySQL 字段类型 decimal(8,2) 的字段

图1



`return_package_length` decimal(8,2) NOT NULL DEFAULT '0.00' COMMENT '退货包裹长度(厘米)'


2、最终实现如下


        $validator = Validator::make(
            $params,
            [
                'return_package_length' => [
                    'numeric',
                    'min:0.00',
                    'max:999999.99',
                    'regex:/^\d{0,6}(\.\d{1,2})?$/'
                ],
            ],
            [

            ]
        );
        if ($validator->stopOnFirstFailure()->fails()) {
            throw new BusinessException(BusinessException::MODULE_ORDER, $validator->getMessageBag()->first());
        }



3、分别尝试:-4(失败)、0(成功)、5(成功)、999999.99(成功)、999999.990(成功)、999999.991(失败)、9999990.99(失败)、0.09(成功)、0.009(失败)、0.0000001(失败),符合预期。如图2
分别尝试:-4(失败)、0(成功)、5(成功)、999999.99(成功)、999999.990(成功)、999999.991(失败)、9999990.99(失败)、0.09(成功)、0.009(失败)、0.0000001(失败),符合预期

图2

]]>
https://www.shuijingwanwq.com/2024/07/05/8699/feed/ 0