引言:聊聊开发中那些让人头秃的查询逻辑

各位好,我是零点博客的博主。最近在维护老项目的时候,发现很多早期的代码在处理多条件搜索时,依然大量依赖直接拼接字符串,也就是所谓的“字符串注入”。这种方法不仅效率低,简直是SQL注入的重灾区。今天咱们就用 C# 和 PHP(EMLOG环境)两种后端视角,手把手教大家如何优雅、安全地编写SQL语句,并搭建一个通用的动态查询框架。

一、 为什么直接拼接字符串是“坑”

很多新手写SQL语句时,习惯把变量直接塞进字符串里。比如:

// PHP/EMLOG 这种写法非常危险!
$sql = "SELECT * FROM " . DB_PREFIX . "blog WHERE title LIKE '%$keyword%'";

看似没问题,但如果用户输入了 `` 作为搜索词,这就会直接暴露在 MySQL 的执行结果中,甚至被恶意利用。

二、 核心大招:预处理语句

无论是写 C# 的 SqlCommand 还是 PHP 的 PDO,预处理语句都是第一道防线。它将SQL语句的“骨架”与“血肉”分离,数据库只负责解析骨架,执行时才注入参数,从底层切断注入风险。

三、 实战演练:多条件动态查询构建

在实际开发中,搜索往往不是单一的。我们需要根据前端传来的参数(标题、分类、时间、作者)来动态生成SQL语句。这里有两种经典的实现思路。

方案A:C# 基础语法拼接(适合理解逻辑)

C# 中我们通常构建 StringBuilder,拼接字符串。注意看这里是如何通过变量控制是否添加条件,以及使用参数化查询的。

public List<Article> SearchArticles(string keyword, int? categoryId, DateTime? startDate)
{
    var list = new List<Article>();
    var sql = new StringBuilder("SELECT * FROM Article WHERE 1=1");

    // 1. 拼接标题搜索条件
    if (!string.IsNullOrEmpty(keyword))
    {
        sql.Append(" AND Title LIKE @Title");
        parameters.Add(new SqlParameter("@Title", "%" + keyword + "%"));
    }

    // 2. 拼接分类条件
    if (categoryId.HasValue)
    {
        sql.Append(" AND CategoryId = @CategoryId");
        parameters.Add(new SqlParameter("@CategoryId", categoryId.Value));
    }

    // 3. 拼接日期范围
    if (startDate.HasValue)
    {
        sql.Append(" AND CreateTime >= @StartDate");
        parameters.Add(new SqlParameter("@StartDate", startDate.Value));
    }

    // 4. 排序
    sql.Append(" ORDER BY CreateTime DESC");

    // 执行查询
    using (var cmd = new SqlCommand(sql.ToString(), conn))
    {
        // 填充参数... 
        // ... 执行读取逻辑 
    }
}

方案B:EMLOG + PHP 原生预处理(更轻量级)

在 EMLOG 这种轻量级 PHP 程序中,为了减少依赖库,我们有时需要原生处理。下面是一个模拟的 `where` 条件拼接辅助函数:

function buildWhereSql(array $params)
{
    $where = [];
    $bindValues = [];

    // 模糊查询 title
    if (!empty($params['keyword'])) {
        $where[] = "title LIKE :title";
        $bindValues[':title'] = '%' . $params['keyword'] . '%';
    }

    // 精确查询 cid (分类)
    if (!empty($params['cid'])) {
        $where[] = "cid = :cid";
        $bindValues[':cid'] = intval($params['cid']);
    }

    // 精确查询 status (状态)
    if (isset($params['status'])) {
        $where[] = "status = :status";
        $bindValues[':status'] = $params['status'];
    }

    // 将数组转换为 'WHERE a=1 AND b=2'
    $sql = "SELECT * FROM " . DB_PREFIX . "blog ";
    if (!empty($where)) {
        $sql .= "WHERE " . implode(' AND ', $where);
    }
    $sql .= " ORDER BY date DESC LIMIT 10";

    return ['sql' => $sql, 'bind' => $bindValues];
}

四、 底层避坑指南:MySQL 性能与索引

写好SQL语句只是第一步,如何让查询飞起来才是关键。

  • 少用 SELECT *: 除非必要,尽量指定字段名。这能减少网络传输和内存消耗。
  • 索引是王道: 如果你在 `WHERE` 子句里加了 `cid` 或者 `date`,务必确保这两个字段上有索引。否则,哪怕加了预处理,数据库引擎也要进行全表扫描,速度会慢得让你想砸键盘。
  • LIKE 陷阱: 在使用 `LIKE '%keyword%'` 进行模糊查询时,如果字段没有索引,索引会直接失效,导致性能崩盘。

五、 总结

这次复盘其实就在做一件事:规范。无论你是用 C# 写复杂的接口,还是用 PHP 开发 EMLOG 插件,保持SQL语句的结构清晰、参数化使用习惯,是减少 Bug 和提高安全性的唯一捷径。希望这篇干货能帮大家少走弯路,代码少跑两遍。