0

0

PHP/MySQLi 优化标签显示:告别 N+1 查询

碧海醫心

碧海醫心

发布时间:2025-10-12 10:43:33

|

914人浏览过

|

来源于php中文网

原创

php/mysqli 优化标签显示:告别 n+1 查询

本教程旨在解决使用 PHP 和 MySQLi 显示标签时常见的 N+1 查询效率问题。通过分析逐个查询标签的低效方法,我们将介绍如何利用 SQL 的 `WHERE IN` 子句,结合预处理语句和动态参数绑定,将多个查询合并为一个高效的数据库操作,显著提升应用程序的性能和响应速度。

标签显示中的 N+1 查询问题

在 Web 开发中,尤其是在处理标签系统时,一个常见且容易被忽视的性能瓶颈是所谓的“N+1 查询问题”。当一个数据行包含多个标签的 ID(例如 1,2,3 这样的字符串),并且需要根据这些 ID 从另一个 tags 表中获取标签名称时,如果不加优化,很容易导致为每个标签 ID 执行一次独立的数据库查询。

考虑以下场景:一个文章可能关联了 5 个标签,它们的 ID 以逗号分隔的形式存储在文章记录中。如果您的代码逻辑是先获取文章记录,然后解析出标签 ID 列表,再对列表中的每个 ID 执行一个 SELECT 查询来获取标签名称,那么您将执行 1(获取文章)+ N(获取 N 个标签)次数据库查询。当 N 增大时,这种方法会迅速拖慢应用程序的性能。

以下是一个典型的低效实现示例:

立即学习PHP免费学习笔记(深入)”;

// 假设 $row["tags"] 的值为 "1,2,3"
$tags = json_decode(json_encode(explode(',', $row["tags"]))); // 示例中这步略显多余,explode已足够

foreach($tags as $tag) {
    $fetchTags = $conn->prepare("SELECT id, name FROM tags WHERE id = ? AND type = 1");
    $fetchTags->bind_param("i", $tag);
    $fetchTags->execute();
    $fetchResult = $fetchTags->get_result();
    if($fetchResult->num_rows === 0) {
        print('No rows');
    }
    while($resultrow = $fetchResult->fetch_assoc()) {
      ?>close();
}

上述代码清晰地展示了 N+1 查询问题:对于 $row["tags"] 中包含的每个标签 ID,都会执行一次 prepare、bind_param、execute 和 close 操作。这不仅增加了数据库的负担,也增加了网络往返的开销,严重影响了性能。

优化方案:使用 WHERE IN 进行单次查询

解决 N+1 查询问题的关键在于将多个独立的查询合并为一个高效的数据库查询。SQL 的 WHERE IN 子句正是为此而生。它允许您在单个查询中指定一组值,匹配其中任何一个值的记录都将被返回。

音剪
音剪

喜马拉雅旗下的一站式AI音频创作平台,强大的在线剪辑能力,帮你轻松创作优秀的音频作品

下载

为了实现这一点,我们需要:

  1. 将逗号分隔的标签 ID 字符串转换为一个 ID 数组。
  2. 动态生成 WHERE IN (?) 子句中的占位符,因为标签的数量是可变的。
  3. 将标签 ID 数组作为参数绑定到预处理语句中。

以下是优化后的实现代码:

prepare('SELECT id, name FROM tags WHERE id IN ('.$placeholders.') AND type = 1 ORDER BY id');

// 4. 动态绑定参数
// str_repeat('s', count($tags)) 生成与标签数量相匹配的类型字符串
// 例如,如果 $tags 包含 3 个元素,则生成 "sss"
// ...$tags (splat operator) 将数组元素作为单独的参数传递给 bind_param
$fetchTags->bind_param(str_repeat('s', count($tags)), ...$tags);

// 5. 执行查询
$fetchTags->execute();

// 6. 获取结果
$fetchResult = $fetchTags->get_result();

if($fetchResult->num_rows === 0) {
    print('No rows');
} else {
    // 遍历结果并显示标签
    foreach($fetchResult as $resultRow) {
        ?>close();
?>

代码解析:

  • explode(',', $row["tags"]): 将标签 ID 字符串拆分为一个数组。
  • array_fill(0, count($tags), '?'): 创建一个包含与标签数量相同问号的数组。
  • implode(',', ...): 将问号数组用逗号连接起来,形成 WHERE IN (?,?,?) 所需的占位符字符串。
  • str_repeat('s', count($tags)): 生成一个字符串,其中包含与标签数量相同的小写字母 's'。这是 bind_param 函数所要求的类型字符串,表示所有参数都是字符串类型。即使 ID 是整数,绑定为字符串通常也能正常工作,并且在参数数量动态变化时简化了类型处理。如果严格要求整数类型,可以使用 'i'。
  • ...$tags: 这是 PHP 5.6+ 的“splat”操作符(也称为参数解包),它将 $tags 数组的每个元素作为单独的参数传递给 bind_param。

PHP 8.1+ 的简化绑定

对于 PHP 8.1 及更高版本,execute() 方法得到了增强,可以直接接受一个数组作为参数,而无需显式调用 bind_param()。这进一步简化了代码:

prepare('SELECT id, name FROM tags WHERE id IN ('.$placeholders.') AND type = 1 ORDER BY id');

// PHP 8.1+ 简化绑定
$fetchTags->execute($tags); // 直接传递数组

$fetchResult = $fetchTags->get_result();

if($fetchResult->num_rows === 0) {
    print('No rows');
} else {
    foreach($fetchResult as $resultRow) {
        ?>close();
?>

这种方式更加简洁,推荐在支持 PHP 8.1+ 的环境中采用。

注意事项与最佳实践

  1. 安全性: 始终使用预处理语句来防止 SQL 注入。上述优化方案正是基于预处理语句实现的,确保了安全性。
  2. 数据验证: 在将 $row["tags"] 字符串传递给 explode() 之前,最好对其进行清理或验证,确保它只包含数字和逗号,避免意外的输入导致错误。
  3. 空标签处理: 在执行 explode() 后,检查 $tags 数组是否为空。如果为空,则表示没有标签需要查询,应避免执行空的 WHERE IN () 查询,这可能导致 SQL 错误或不必要的数据库操作。示例代码中已加入了此检查。
  4. 性能提升: 将 N+1 次查询减少为 1 次查询,可以显著减少数据库连接、查询解析和网络往返的开销,从而大幅提升应用程序的性能和响应速度,尤其是在处理大量数据或高并发请求时。
  5. PDO 的选择: 虽然本教程主要使用 MySQLi,但 PDO (PHP Data Objects) 提供了更一致的数据库抽象层,并且在动态绑定参数方面可能略微更灵活。如果您考虑切换到 PDO,其实现思路与 MySQLi 类似,同样是构建动态占位符并绑定参数。

总结

通过采用 WHERE IN 子句和预处理语句,我们可以有效地将多个独立的数据库查询合并为一个高效的单次查询,从而解决标签显示中的 N+1 查询问题。这种优化不仅提升了应用程序的性能,也使得代码更加健壮和易于维护。在开发任何涉及从关联表中获取多条记录的系统时,都应优先考虑这种批量查询的优化策略。

相关专题

更多
php文件怎么打开
php文件怎么打开

打开php文件步骤:1、选择文本编辑器;2、在选择的文本编辑器中,创建一个新的文件,并将其保存为.php文件;3、在创建的PHP文件中,编写PHP代码;4、要在本地计算机上运行PHP文件,需要设置一个服务器环境;5、安装服务器环境后,需要将PHP文件放入服务器目录中;6、一旦将PHP文件放入服务器目录中,就可以通过浏览器来运行它。

2684

2023.09.01

php怎么取出数组的前几个元素
php怎么取出数组的前几个元素

取出php数组的前几个元素的方法有使用array_slice()函数、使用array_splice()函数、使用循环遍历、使用array_slice()函数和array_values()函数等。本专题为大家提供php数组相关的文章、下载、课程内容,供大家免费下载体验。

1661

2023.10.11

php反序列化失败怎么办
php反序列化失败怎么办

php反序列化失败的解决办法检查序列化数据。检查类定义、检查错误日志、更新PHP版本和应用安全措施等。本专题为大家提供php反序列化相关的文章、下载、课程内容,供大家免费下载体验。

1521

2023.10.11

php怎么连接mssql数据库
php怎么连接mssql数据库

连接方法:1、通过mssql_系列函数;2、通过sqlsrv_系列函数;3、通过odbc方式连接;4、通过PDO方式;5、通过COM方式连接。想了解php怎么连接mssql数据库的详细内容,可以访问下面的文章。

952

2023.10.23

php连接mssql数据库的方法
php连接mssql数据库的方法

php连接mssql数据库的方法有使用PHP的MSSQL扩展、使用PDO等。想了解更多php连接mssql数据库相关内容,可以阅读本专题下面的文章。

1419

2023.10.23

html怎么上传
html怎么上传

html通过使用HTML表单、JavaScript和PHP上传。更多关于html的问题详细请看本专题下面的文章。php中文网欢迎大家前来学习。

1235

2023.11.03

PHP出现乱码怎么解决
PHP出现乱码怎么解决

PHP出现乱码可以通过修改PHP文件头部的字符编码设置、检查PHP文件的编码格式、检查数据库连接设置和检查HTML页面的字符编码设置来解决。更多关于php乱码的问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

1488

2023.11.09

php文件怎么在手机上打开
php文件怎么在手机上打开

php文件在手机上打开需要在手机上搭建一个能够运行php的服务器环境,并将php文件上传到服务器上。再在手机上的浏览器中输入服务器的IP地址或域名,加上php文件的路径,即可打开php文件并查看其内容。更多关于php相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

1306

2023.11.13

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

23

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
MySQL 教程
MySQL 教程

共48课时 | 1.8万人学习

MySQL 初学入门(mosh老师)
MySQL 初学入门(mosh老师)

共3课时 | 0.3万人学习

简单聊聊mysql8与网络通信
简单聊聊mysql8与网络通信

共1课时 | 801人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号