说个很常见的场景:你接手一个五年前的老项目,打开代码发现里面全是 mysql_connect()、mysql_query()、mysql_fetch_assoc() 这种函数。第一次跑起来,页面直接白屏,打开错误日志一看:Call to undefined function mysql_connect()。等你解决了连接问题,往数据库里插入中文记录,又发现存储内容全部变成了一串问号,页面上怎么调 header 都没用,最后才意识到是连接层的字符集没设置。
这篇文章就把这两件事一次性说清楚:PHP 连接 MySQL 数据库的三种主流方式,以及中文乱码的完整解决方案。我会从老 mysql 扩展讲起,再到 mysqli、PDO,最后给出一个可以直接抄走的图书管理小例子。无论你是刚学 PHP 的入门开发者,还是正在维护老项目的“救火队员”,这篇内容都值得看完。
1. 整体设计与思路拆解:先别急着敲代码,想清楚再动手
1.1 还没动手就赢一半:三种MySQL连接方式横向对比
在 PHP 里连 MySQL,历史上和现在常用的一共三条路:老 mysql 扩展、mysqli 扩展、PDO 扩展。很多人刚接触时容易懵,不知道用哪个。我从实际使用的角度,先把它们的核心差异摆出来。
| 对比项 | mysql扩展(老) | mysqli扩展 | PDO扩展 |
|---|---|---|---|
| 版本历史 | PHP 4/5 早期 | PHP 5.3+ 推荐 | PHP 5.1+ 出现 |
| 当前状态 | PHP 5.5 弃用,PHP 7 移除 | 正常维护 | 正常维护 |
| 面向对象支持 | 不支持 | 支持 | 支持 |
| 预处理语句 | 不支持 | 支持 | 支持 |
| 数据库兼容 | 仅 MySQL | 仅 MySQL | 支持 MySQL、PostgreSQL、SQLite 等 |
| 典型场景 | 老项目维护 | 传统 PHP 项目 | 新项目、框架项目 |
看完表格你应该明白了:标题里说的“mysql扩展”,在今天的语境下已经不能简单等同于老 mysql_* 函数族了,而是要理解成“PHP 连接 MySQL 所需的扩展方案”这个整体。实际开发中,维护老代码时你会见到老 mysql 扩展,写新功能时基本绕不开 mysqli 和 PDO,这就是为什么我建议把三条路都搞清楚。
1.2 为什么“经典实践”可以老,但不能老旧
总有人问我:既然老 mysql 扩展的教程那么多,照着写不就行了?这里有个非常现实的问题:PHP 官方从 5.5.0 开始把老 mysql 扩展标记为废弃,到 PHP 7.0 直接移除了。你现在装个 PHP 7.4 或 PHP 8.x,环境里根本找不到 mysql_connect() 这个函数。所以如果你在搜索引擎里看到的教程还是清一色 mysql_connect(),请直接关掉,那套写法已经过时了。
老扩展被移除的核心原因有三个:
- 它只支持面向过程的调用风格,代码写多了非常散乱,难以维护。
- 不支持预处理语句,防 SQL 注入完全靠手动转义,很容易漏。
- 官方维护精力早已转移到
mysqli和PDO,老扩展安全漏洞得不到及时修复。
我处理过一个真实案例:客户服务器上的 PHP 还是 5.2 版本,跑着一个十年前的商品系统,里面全是老 mysql 扩展。后来服务器被迫升级,PHP 版本一高,全站直接白屏,代码里上百个 mysql_query() 全部报错。后来我写了一层兼容函数,把老函数名映射到 mysqli_* 实现,才平稳过渡。这件事给我的教训就是:经典经验要吸收,但代码必须跟着时代走。
1.3 中文乱码的根因:链路不一致才导致乱码
中文乱码这个问题,十个人有九个会告诉你“设置成 UTF-8 就行了”,但真操作起来还是乱,原因在于字符集是一条链路,不是单点配置。通俗来说,你写中文、存中文、读中文、显示中文,相当于让几个人用一套统一的“字典”交流。只要中间某个人手里拿的是另一版字典,内容就成了天书。
完整链路有五个环节:
- 数据源头,比如用户输入、导入文件本身的编码。
- PHP 脚本文件本身的保存编码。
- HTTP 响应头里声明的
charset。 - 连接 MySQL 时的客户端字符集。
- 数据库表、字段自身的字符集。
这五个环节只要有一个是 latin1 或 gbk,其他都是 utf8mb4,中文就会在你意想不到的地方花式乱码。很多人在页面头部加了 header('Content-Type: text/html; charset=utf-8'),以为万事大吉,结果数据库连接层还是默认的 latin1,查出来的中文一样是问号。所以在设计阶段就得把这条链路定死,而不是出了乱码再到处试。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点:从环境准备到三种写法
2.1 环境准备:先确认MySQL扩展真的加载了
写代码之前,先确认当前 PHP 环境里到底有没有 MySQL 相关扩展。命令行执行:
bash复制php -v
确认 PHP 版本后,再看扩展加载情况:
bash复制php -m | grep -i -E 'mysql|pdo'
正常情况下至少能看到 mysqli 和 pdo_mysql 中的一个。如果啥都没有,就需要去 php.ini 里启用扩展,把对应的行前面的分号去掉:
ini复制extension=mysqli
extension=pdo_mysql
Windows 环境下,extension_dir 也要确认指向了正确的 ext 目录,否则扩展文件找不到。改完 php.ini 后,记得重启 PHP-FPM 或 Apache,不然配置不生效。如果你用的是集成环境,比如 phpStudy、Laragon 这类工具,直接在面板里勾选扩展即可,原理一样。
对于老项目,偶尔还会看到 extension=php_mysql.dll 这种配置。如果当前 PHP 版本是 7.0 以上,这行配置直接删掉就行,因为对应的扩展文件已经不存在了。这里也顺便提一句:判断一个扩展是否生效,最直观的方式是写一个只有 <?php phpinfo(); 的文件,浏览器打开后在页面里搜索 mysqli 或 pdo_mysql 的关键字,能看到对应区块就说明加载成功。
2.2 火星时代的连接写法:老mysql扩展怎么读
下面这段是老 mysql 扩展的典型写法,虽然新项目不允许这样写,但你在维护老代码时很可能遇到,所以我仍然给出示例,并逐行拆解,目的是让你看得懂,而不是让你照着写。
php复制<?php
// 老mysql扩展:仅用于理解历史代码,禁止在新项目中使用
$conn = mysql_connect('localhost', 'root', '123456') or die('连接失败: ' . mysql_error());
mysql_select_db('shop', $conn);
mysql_query('SET NAMES utf8mb4', $conn);
$res = mysql_query('SELECT id, title FROM article', $conn);
while ($row = mysql_fetch_assoc($res)) {
echo $row['title'];
}
mysql_close($conn);
?>
这段代码的流程是:mysql_connect() 建立连接,mysql_select_db() 选择数据库,mysql_query() 执行 SQL,mysql_fetch_assoc() 取结果,最后 mysql_close() 关闭连接。注意这里手动执行了 SET NAMES utf8mb4,这在老扩展时代几乎是标配,但新扩展有更规范的字符集设置方式,后面会细讲。
在维护老代码时,你还要特别小心两件事。第一,老代码几乎不用预处理,SQL 拼接随处可见,接手后第一优先级是排查所有 $_GET、$_POST 直接进 SQL 的地方,不然迟早被注入。第二,老代码里 mysql_connect() 在同一进程内、相同参数的情况下,会复用之前的连接,并不会真的返回新连接,这在并发场景下坑过不少人。建议维护这类代码时,逐步向 mysqli 或 PDO 迁移。
2.3 最常用的替代方案:mysqli的过程式与面向对象写法
mysqli 是 MySQL Improved 的缩写,可以理解成老 mysql 扩展的加强版。它保留了老扩展容易上手的特点,同时支持预处理和面向对象调用。先看过程式写法:
php复制<?php
// mysqli过程式写法
$conn = mysqli_connect('127.0.0.1', 'root', '123456', 'shop');
if (!$conn) {
die('连接失败: ' . mysqli_connect_error());
}
mysqli_set_charset($conn, 'utf8mb4');
$sql = 'SELECT id, title FROM article WHERE id = ?';
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'i', $id);
$id = 1;
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['title'];
}
mysqli_stmt_close($stmt);
mysqli_close($conn);
?>
再看面向对象写法,这也是我更推荐的方式,代码结构明显更紧凑:
php复制<?php
// mysqli面向对象写法
$mysqli = new mysqli('127.0.0.1', 'root', '123456', 'shop');
if ($mysqli->connect_errno) {
die('连接失败: ' . $mysqli->connect_error);
}
$mysqli->set_charset('utf8mb4');
$stmt = $mysqli->prepare('SELECT id, title FROM article WHERE id = ?');
$stmt->bind_param('i', $id);
$id = 1;
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
echo $row['title'];
}
$stmt->close();
$mysqli->close();
?>
看到区别了吧?面向对象写法把 mysqli_ 前缀的函数收拢成了对象方法,读起来逻辑更顺。两种写法在性能上没有本质差别,选哪种主要看你项目里的既有风格。我的建议是,新代码直接用面向对象方式,既符合现代 PHP 习惯,也为以后阅读框架源码打下基础。
这里有个细节要注意:mysqli 连接参数里的 127.0.0.1 和 localhost 不完全等价。localhost 可能走 Unix Socket,127.0.0.1 走 TCP。在部分系统上,Socket 的认证方式、权限控制和服务端口跟 TCP 不一致,容易出现“换了个写法就连接失败”的怪问题。排查连接问题时,可以先试试把 localhost 改成 127.0.0.1,能排除不少干扰项。
2.4 PDO:新项目的更优起跑线
PDO 的全称是 PHP Data Objects,它不单单是 MySQL 的驱动,而是一个数据库抽象层。我对新人一贯的建议是:如果项目没有历史包袱,直接上 PDO。理由很简单,它换数据库时不需要重写业务逻辑,预处理、事务支持都做得更顺手。
php复制<?php
// PDO连接示例
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=shop;charset=utf8mb4';
$user = 'root';
$pass = '123456';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
die('连接失败: ' . $e->getMessage());
}
$stmt = $pdo->prepare('SELECT id, title FROM article WHERE id = ?');
$stmt->execute([1]);
$rows = $stmt->fetchAll();
foreach ($rows as $row) {
echo $row['title'];
}
?>
注意 DSN 里的 charset=utf8mb4,这相当于在连接建立时就把字符集设定好了。PDO::ATTR_EMULATE_PREPARES 设为 false,是让数据库自己完成预处理,而不是交给 PDO 模拟,在防止注入时更可靠。
PDO 最让人舒服的一点是,你如果用 mysql 开发完功能,哪天老板说要把数据迁到 PostgreSQL,代码里需要修改的通常只有 DSN 那一段,而不是翻遍整个项目的 SQL。当然,SQL 方言差异仍然存在,但连接层的痛苦确实少了很多。
3. 中文编码解决方案:把乱码一网打尽
3.1 为什么我强烈建议用utf8mb4,而不是utf8
很多人建表时习惯写 DEFAULT CHARSET=utf8,这个习惯得改改。MySQL 里的 utf8 其实是 utf8mb3,最多只能存 3 字节的字符。这意味着什么?意味着 Emoji 表情、部分生僻汉字,比如“𠀀”这类 4 字节字符,直接存不进去,要么报错,要么被转成问号。
utf8mb4 才是真正意义上完整的 UTF-8 实现,它最多支持 4 字节字符。MySQL 从 5.5.3 开始引入 utf8mb4,新版本 MySQL 8.0 更把默认字符集直接改成了 utf8mb4。所以现在的建表语句,我建议统一写成:
sql复制CREATE TABLE `article` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL COMMENT '标题',
`content` TEXT NULL COMMENT '内容',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
COLLATE=utf8mb4_unicode_ci 是字符集排序规则,unicode_ci 在比较和排序时更符合一般预期。MySQL 8.0 也可以用默认的 utf8mb4_0900_ai_ci,两种都能选,统一就行。还有一个容易踩的坑:utf8mb4 下每个字符最多占 4 字节,做索引时长度限制更严格,比如 VARCHAR(255) 直接加普通索引,在旧版本 InnoDB 上可能触发 “Specified key was too long” 错误。解决办法是减小字段长度,或者用前缀索引,比如 INDEX idx_title (title(191))。
3.2 连接层字符集:别再手动拼SQL SET NAMES
老教程里经常看到这样的代码:
php复制mysql_query('SET NAMES utf8mb4');
在 mysqli 和 PDO 里,完全不建议再用这种方式。正确做法是用各自的字符集 API:
php复制// mysqli
$mysqli->set_charset('utf8mb4');
// PDO
$dsn = 'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4';
为什么不推荐手动 SET NAMES?因为 SET NAMES utf8mb4 是把 character_set_client、character_set_connection、character_set_results 三个系统变量一口气改掉,操作上并没有错。但用 API 方式设置,是由 MySQL 官方驱动的底层接口完成的,语义更清晰,也不容易在拼接 SQL 时引发编码转换意外。在老扩展时代,mysql_set_charset() 也是同样优先的写法。
连接建立后,你可以随时检查当前连接的字符集状态:
sql复制SHOW VARIABLES LIKE 'character_set%';
重点看这几项:character_set_client、character_set_connection、character_set_results 是否都是 utf8mb4。这三个值如果不一致,就可能出现“写进去是乱码,读出来也是乱码”的连锁反应。
3.3 输出层:header、JSON和万恶的BOM
数据库和连接层的字符集理顺之后,最后一步是页面输出。HTML 页面要明确声明:
php复制header('Content-Type: text/html; charset=utf-8');
输出 JSON 接口时同样要声明,否则浏览器可能会按默认编码去解析,中文直接变乱码:
php复制header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
JSON_UNESCAPED_UNICODE 这个参数务必加上。不加的话,json_encode 会把中文转成 \uXXXX 形式的 Unicode 转义序列,虽然数据没错,但可读性很差,别人调试接口时要多花时间。
还有一个隐蔽性极强的坑:BOM 头。你用 Windows 上的记事本编辑 PHP 文件,如果保存的时候选了“UTF-8”格式,记事本会偷偷在文件最前面加上 EF BB BF 三个字节的 BOM。这三个字节在 PHP 输出阶段会被当成普通内容直接发送给浏览器,造成最经典的报错:
text复制Warning: Cannot modify header information - headers already sent
因为 header 必须在任何输出之前发送,BOM 抢先输出了,所有 header() 调用直接失败,JSON 接口也会在返回内容最前面多出几个不可见字符,前端 JSON.parse 直接报错。解决办法很简单:编辑器里保存 PHP 文件时,编码一律选“UTF-8 无 BOM”。现在主流编辑器 VSCode、PhpStorm 默认就是无 BOM,但用记事本编辑过的文件仍要留个心眼。
3.4 一套可执行的乱码排查顺序
我把排查乱码的顺序总结成清单,新项目从一开始就按这个顺序做,老项目出了问题也按这个顺序翻:
- 打开表结构,确认表、字段字符集是
utf8mb4。 - 检查连接层字符集,
mysqli用set_charset('utf8mb4'),PDO 检查 DSN 里的charset=utf8mb4。 - 确认 PHP 文件本身保存为 UTF-8 无 BOM。
- 确认页面或接口输出前设置了正确的
header('Content-Type: ...; charset=utf-8')。 - 查看数据源头,导入的 CSV、Excel、SQL 文件本身是什么编码。
如果按这个顺序查完还是乱码,再考虑一个极端情况:历史数据已经以错误编码存进数据库了,比如内容在 latin1 字段里存了 UTF-8 字节流。这种情况单纯改连接字符集解决不了,通常需要先用数据库工具导出、转码、再重新导入。改字段字符集前,一定要先备份数据,这是一个无数人踩过的教训。
4. 实操过程与核心环节实现:手写一个中文图书管理示例
4.1 从零搭建:建表、连库、插入中文、查询输出
我再给一个完整的、可以直接运行的最小示例。场景是做图书分类管理,功能包括建表、插入中文数据、查询列表,最后输出成 JSON。
先建表:
sql复制CREATE DATABASE IF NOT EXISTS `book_manager`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE `book_manager`;
CREATE TABLE IF NOT EXISTS `book` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(200) NOT NULL COMMENT '书名',
`author` VARCHAR(100) NOT NULL COMMENT '作者',
`category` VARCHAR(50) NOT NULL COMMENT '分类',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
然后是 PHP 代码,用 PDO 连接:
php复制<?php
header('Content-Type: application/json; charset=utf-8');
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=book_manager;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '123456', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
// 插入中文数据
$stmt = $pdo->prepare('INSERT INTO book (name, author, category) VALUES (?, ?, ?)');
$stmt->execute(['深入理解MySQL', '苏三', '数据库']);
$stmt->execute(['PHP实战之道', '张三', 'PHP']);
// 查询并按分类分组输出
$stmt = $pdo->query('SELECT category, COUNT(*) AS cnt FROM book GROUP BY category');
$stats = $stmt->fetchAll();
echo json_encode([
'code' => 0,
'message' => 'success',
'data' => $stats,
], JSON_UNESCAPED_UNICODE);
这段代码在前面的技术点基础上做了组合:DSN 里指定了 charset=utf8mb4,输出 JSON 时用了 JSON_UNESCAPED_UNICODE,连接和输出两个最容易乱码的口子都堵住了。把这段代码保存成 UTF-8 无 BOM 格式,放到站点目录下直接访问,如果一切正常,你会看到类似下面的输出:
json复制{"code":0,"message":"success","data":[{"category":"数据库","cnt":1},{"category":"PHP","cnt":1}]}
中文没有变 \uXXXX,也没有乱码,就可以放心在此基础上扩展了。
4.2 简单封装:一个MiniDB类
如果每个页面都写一遍 new PDO(...),项目一复杂就到处是重复代码。我习惯做个最小封装,把连接和常见的增删改查收敛到一个类里:
php复制<?php
class MiniDB
{
private static $pdo = null;
public static function connection(): PDO
{
if (self::$pdo === null) {
$dsn = 'mysql:host=127.0.0.1;port=3306;dbname=book_manager;charset=utf8mb4';
self::$pdo = new PDO($dsn, 'root', '123456', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return self::$pdo;
}
public static function query(string $sql, array $params = []): array
{
$stmt = self::connection()->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
public static function execute(string $sql, array $params = []): int
{
$stmt = self::connection()->prepare($sql);
$stmt->execute($params);
return $stmt->rowCount();
}
}
// 使用
$books = MiniDB::query('SELECT * FROM book WHERE category = ?', ['PHP']);
这个类刻意做得非常简单,没有搞复杂的事件、钩子、链式调用,够用就行。核心意图是把“连接怎么建、字符集怎么设”这个容易出错的地方收敛在一个文件里,以后团队所有人写代码,都走同一个入口,字符集配置就再也错不到哪里去。新手可以在这个基础上慢慢加日志、加事务方法,但不要一上来就整一堆抽象层。
5. 常见问题与排查技巧实录
5.1 高频报错速查表
我把实际运维中遇到的高频问题整理成一张速查表,方便你直接对照。
| 报错信息 | 原因定位 | 解决办法 |
|---|---|---|
Call to undefined function mysql_connect() |
PHP 7+ 已移除老mysql扩展 | 改用 mysqli 或 PDO |
Connection refused |
MySQL 服务未启动或端口不对 | 检查服务状态、监听端口 |
Access denied for user 'root'@'localhost' |
账号密码错误或权限不足 | 核对密码,确认远程/本地授权 |
Unknown database 'book_manager' |
数据库不存在 | 先创建数据库,确认库名 |
Table 'xxx' doesn't exist |
表名错误或连错数据库 | 检查表名、库名前缀 |
Cannot modify header information - headers already sent |
输出前已有内容,常为BOM或空格 | 文件另存为UTF-8无BOM,检查 <?php 前无输出 |
JSON.parse: unexpected character |
返回内容里混入了BOM或调试输出 | 去掉BOM,清理页面其他 echo |
SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client |
MySQL 8 默认认证插件与老客户端不兼容 | 升级 PHP,或测试环境将用户改为 mysql_native_password |
Server sent charset (255) unknown to the client |
老版本 PHP 不认识 utf8mb4 | 升级 PHP,使用 mysqlnd 驱动 |
最后两行特别提一下:如果你的 MySQL 是 8.0,而 PHP 版本比较老,连接时很可能碰上认证插件不兼容的问题。MySQL 8 默认的认证插件是 caching_sha2_password,旧版 mysqlnd 客户端不认。优先方案是升级 PHP,实在没法升级,只能在测试环境给该用户改回老认证方式,生产环境建议走正规升级路线。
5.2 一次真实的乱码排查过程
有一回我帮朋友调一个笔记系统的中文乱码,现象很典型:在管理后台输入“你好世界”,保存后页面显示“浣犲ソ涓栫晫”。这类乱码的名字很形象,一看就是 UTF-8 的字节被 GBK 或者 latin1 错误解读了。
我按顺序排查:
- 先看页面输出,
header确实设置了 UTF-8,排除 HTTP 层问题。 - 打开数据库客户端,直接看表里的原始数据,发现数据本身就是错的,说明写入环节就出了问题。
- 查看连接字符集,PHP 代码里用的是
mysqli,但没调用set_charset,默认连接字符集还是latin1。 - 查看表结构,表字段是
utf8_general_ci,但连接层用了latin1,于是写入时中文被转成了错误字节流。
修复方案是:先用工具把错误的数据导出,转码后再重新导入,然后把 PHP 代码里补上 $mysqli->set_charset('utf8mb4'),把连接层和表结构对齐。这个问题耗时半小时,真正的原因只有一行代码。也是从那次之后,我写项目启动文档时,一定会把“连接字符集统一 utf8mb4”写成强制规范。
5.3 几个我想单独提醒的坑
很多新手在本地用集成环境开发,一切正常,代码传到服务器就白屏。第一个要检查的就是 php.ini 改完后有没有重启 PHP-FPM。另一个是扩展没装对,本地用的 mysqli,服务器上偏偏只开了 pdo_mysql,页面直接报 undefined function。上线前在服务器命令行执行一遍 php -m,就能把这类问题提前消灭。
长连接也是个容易出事的点。老 mysql 扩展时代,mysql_pconnect() 是长连接,很多老代码喜欢用它提高性能,但长连接如果使用不当,容易把连接数打满,特别是在 FastCGI 模式下。新代码我用 PDO 时一般保持短连接,用完就释放,性能瓶颈很少发生在连接建立上,真要优化连接开销,优先考虑加一层连接复用方案,而不是盲目上长连接。
最后提醒一句:改数据库结构、批量更新数据这类操作,无论操作有多简单,先备份。一句 UPDATE 忘加 WHERE 导致全表数据被清空的案例,技术社区里一抓一大把。用 mysqldump 导出一份备份再动手,成本极低,收益极高。
回到开头那个维护老项目的场景,我后来处理的方式是:给那台老服务器写了一个兼容层,把老 mysql_* 函数映射到 mysqli 实现,同时新建功能全部用 PDO,并且把连接入口统一收敛到一个文件里,字符集固定写成 utf8mb4。这个习惯救了我很多次,至少少了不知道多少个“本地好好的,一部署就乱码”的深夜排查。也希望你从第一个项目起,就把字符集这条链路在代码里固定下来,这种事前设计,比事后救火省力太多。
