
本教程探讨了在使用 php/mysqli 从数据库中高效检索和显示多个标签的方法。针对传统逐个查询标签的低效问题,我们介绍并演示了如何利用 mysqli 的 `where in` 子句,通过单次数据库查询批量获取标签数据,从而显著提升性能和资源利用率,避免 n+1 查询陷阱,并兼容 php 8.1+ 的简化执行方式。
在现代网站应用中,标签系统(Tagging System)是常见的功能,用于内容的分类、检索和关联。然而,如果不恰当地实现标签的获取和显示逻辑,可能会导致严重的性能问题,尤其是在需要显示大量标签的页面上。本文将深入探讨一种常见的低效实现方式,并提供一种利用 MySQLi 的 WHERE IN 子句进行优化的方案,从而显著提升数据查询效率。
一个常见的低效模式是所谓的“N+1 查询问题”。当一个内容项关联了多个标签时,如果采用为每个标签ID执行一次独立的数据库查询来获取标签名称的方式,那么对于 N 个标签,就需要执行 N 次查询,再加上获取内容本身的一次查询,总共就是 N+1 次查询。
考虑以下场景:一个内容项的标签ID以逗号分隔的字符串形式存储,例如 1,2,3。原始的标签获取和显示代码可能如下所示:
<?php
// 假设 $conn 是已建立的 MySQLi 数据库连接
// 假设 $row["tags"] 的值为 "1,2,3"
$tags = json_decode(json_encode(explode(',', $row["tags"]))); // 将字符串转换为数组,这里多了一步不必要的json操作
foreach($tags as $tag) {
    // 为每个标签ID执行一次独立的查询
    $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()) {
      ?><span class="badge bg-primary me-2"><?php echo htmlspecialchars($resultrow["name"]); ?></span><?php
    }
    $fetchTags->close(); // 每次循环都关闭预处理语句
}
?>这段代码的问题在于,如果 $row["tags"] 包含 5 个标签ID,它将执行 5 次独立的 SQL 查询。每次查询都涉及数据库连接、查询解析、数据传输等开销。当标签数量增多时,这种开销会迅速累积,导致页面加载缓慢,服务器资源消耗增加。
立即学习“PHP免费学习笔记(深入)”;
解决 N+1 查询问题的核心思想是将多个单行查询合并为一个多行查询。MySQLi 提供了 WHERE IN 子句,允许我们指定一个值列表,匹配其中任何一个值的记录。例如:SELECT * FROM table WHERE id IN (1, 2, 3)。
通过这种方式,我们可以将所有标签ID作为列表传递给 WHERE IN 子句,从而只需一次数据库查询就能获取所有相关标签的名称。
以下是使用 WHERE IN 子句优化标签获取的 PHP/MySQLi 实现:
<?php
// 假设 $conn 是已建立的 MySQLi 数据库连接
// 假设 $row["tags"] 的值为 "1,2,3"
// 1. 将逗号分隔的标签ID字符串转换为数组
$tags = explode(',', $row["tags"]);
// 2. 为 WHERE IN 子句动态生成占位符
// 例如,如果 $tags 包含 3 个元素,则生成 "?,?,?"
$placeholders = implode(',', array_fill(0, count($tags), '?'));
// 3. 构建包含 WHERE IN 子句的 SQL 查询
// 注意:type = 1 作为一个额外的筛选条件
$fetchTags = $conn->prepare('SELECT id, name FROM tags WHERE id IN ('.$placeholders.') AND type = 1 ORDER BY id');
// 4. 绑定参数
// str_repeat('s', count($tags)) 生成与标签数量相同个数的类型字符串,例如 "sss"
// ...$tags 使用 PHP 的展开运算符(spread operator),将 $tags 数组的元素作为独立的参数传递给 bind_param
$fetchTags->bind_param(str_repeat('s', count($tags)), ...$tags);
// 5. 执行查询
$fetchTags->execute();
// 6. 获取结果
$fetchResult = $fetchTags->get_result();
// 7. 处理无结果情况(可选)
if($fetchResult->num_rows === 0) {
    // print('No rows'); // 根据实际需求处理
}
// 8. 遍历结果并显示标签
foreach($fetchResult as $resultrow) {
    ?><span class="badge bg-primary me-2"><?php echo htmlspecialchars($resultrow['name']); ?></span><?php
}
// 查询完成后,不需要在循环中关闭,只需关闭一次即可
$fetchTags->close();
?>从 PHP 8.1 开始,mysqli_stmt::execute() 方法可以直接接受一个数组作为参数,而无需显式调用 bind_param()。这进一步简化了代码:
<?php
// ... (前几步与上面相同,直到构建 $fetchTags 预处理语句)
// 假设 $tags 数组已准备好
// $tags = explode(',', $row["tags"]);
// $placeholders = implode(',', array_fill(0, count($tags), '?'));
// $fetchTags = $conn->prepare('SELECT id, name FROM tags WHERE id IN ('.$placeholders.') AND type = 1 ORDER BY id');
// 在 PHP 8.1 及更高版本中,可以直接这样执行
$fetchTags->execute($tags);
// ... (后续获取结果和显示标签的代码相同)
?>这种简化使得代码更加简洁易读,并且类型推断通常能够正确处理参数类型。
通过将多个独立的标签查询合并为一次使用 WHERE IN 子句的批量查询,我们可以显著优化 PHP/MySQLi 应用程序中标签显示功能的性能。这种方法有效解决了 N+1 查询问题,减少了数据库交互次数,从而提升了整体系统效率和用户体验。对于 PHP 8.1 及更高版本,execute() 方法的简化参数传递方式进一步提高了代码的简洁性。在开发过程中,始终关注数据库交互的效率,是构建高性能应用的关键。
以上就是优化 PHP/MySQLi 标签显示:告别 N+1 查询问题的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号