PHP 实现 CSV 文件安全导入 MySQL 表的完整教程

5次阅读

PHP 实现 CSV 文件安全导入 MySQL 表的完整教程

本文详解如何使用 php 的 LOAD DATA LOCAL INFILE 安全、可靠地将本地上传的 csv 文件批量导入 mysql 数据库表,涵盖路径处理、SQL 语法修正、权限配置及常见错误规避。

本文详解如何使用 php 的 `load data local infile` 安全、可靠地将本地上传的 csv 文件批量导入 mysql 数据库表,涵盖路径处理、sql 语法修正、权限配置及常见错误规避。

在 Web 应用中,通过表单上传 CSV 并批量写入数据库是高频需求。但直接使用 LOAD DATA LOCAL INFILE 时极易因路径误用、引号缺失或权限限制而报错(如 SQLSTATE[42000]: Syntax Error… near ‘student.csv INTO table…’)。根本原因在于:LOAD DATA LOCAL INFILE 要求文件路径指向数据库服务器上的可访问位置,而非客户端原始文件名;且文件名必须用单引号包裹

以下为经过生产验证的完整实现方案:

✅ 正确步骤与关键修复点

  1. 获取并保存临时文件到服务端可读路径
    浏览器上传的文件仅暂存于 $_FILES[“myfile”][“tmp_name”],需主动移动至脚本同级(或其他指定)目录:

    $uploadedFile = $_FILES["myfile"]["name"]; $targetPath = "./" . basename($uploadedFile); if (!move_uploaded_file($_FILES["myfile"]["tmp_name"], $targetPath)) {     die("文件上传失败:请检查目录是否具有写入权限(chmod 755 或 777)"); }
  2. 构造合法 SQL —— 必须使用单引号包裹文件路径
    原代码中 $uploadedFile 未加引号,导致 SQL 解析为 LOAD DATA LOCAL INFILE student.csv …(非法标识符),正确写法为:

    $sql = "LOAD DATA LOCAL INFILE '" . $targetPath . "'          INTO TABLE " . $studentTable . "          FIELDS TERMINATED BY ','          LINES TERMINATED BY 'n'          IGNORE 1 LINES";

    ⚠️ 注意:LINES TERMINATED BY ‘n’ 适用于 unix/linux 换行;若 CSV 来自 windows,应改为 ‘rn’;Mac 可能为 ‘r’。建议统一用 file() + fgetcsv() 预检首行换行符。

  3. pdo 连接必须启用 LOCAL_INFILE 支持
    除连接选项外,还需确保 MySQL 服务端允许该操作(部分托管环境默认禁用):

    $conn = new PDO(     "mysql:host=$servername;dbname=$dbname",     $username,     $password,     [         PDO::MYSQL_ATTR_LOCAL_INFILE => true, // 启用 LOCAL INFILE         PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION     ] );

? 安全与稳定性增强建议

  • 路径白名单校验:防止目录遍历攻击

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

    $safeName = preg_replace('/[^a-zA-Z0-9_.-]/', '', $uploadedFile); // 仅保留安全字符 $targetPath = "./uploads/" . $safeName; // 推荐专用 uploads/ 子目录
  • CSV 格式预校验(可选但推荐)
    在执行 LOAD DATA 前,用 fgetcsv() 读取前几行验证字段数、编码(UTF-8 bom)、分隔符一致性:

    $handle = fopen($targetPath, 'r'); $firstRow = fgetcsv($handle, 0, ','); fclose($handle); if (!$firstRow || count($firstRow) !== 5) { // 假设目标表有 5 列     throw new Exception("CSV 列数不匹配,请检查格式"); }
  • MySQL 服务端配置确认
    执行 SHOW VARIABLES LIKE ‘local_infile’;,确保值为 ON;若为 OFF,需在 MySQL 配置文件中添加:

    [mysqld] local_infile=ON [client] local_infile=ON

    并重启服务(部分云数据库需在控制台开启「本地文件导入」开关)。

✅ 最终可运行示例(精简版)

<?php $servername = "localhost"; $username = "root"; $password = ""; $dbname = "onlinepaydb"; $studentTable = "studentregtable";  if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_FILES["myfile"])) {     $originalName = $_FILES["myfile"]["name"];     $safeName = preg_replace('/[^a-zA-Z0-9_.-]/', '', $originalName);     $targetPath = "./uploads/" . $safeName;      // 创建 uploads 目录(若不存在)     if (!is_dir("./uploads")) mkdir("./uploads", 0755, true);      if (!move_uploaded_file($_FILES["myfile"]["tmp_name"], $targetPath)) {         die("文件保存失败,请检查 ./uploads 目录权限");     }      try {         $conn = new PDO(             "mysql:host=$servername;dbname=$dbname",             $username,             $password,             [PDO::MYSQL_ATTR_LOCAL_INFILE => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]         );          $sql = "LOAD DATA LOCAL INFILE '" . $targetPath . "'                  INTO TABLE " . $studentTable . "                  FIELDS TERMINATED BY ','                  LINES TERMINATED BY 'n'                  IGNORE 1 LINES";          $conn->exec($sql);         echo "<p style='color:green'>✅ 成功导入:" . $conn->lastInsertId() . " 行数据</p>";      } catch (PDOException $e) {         echo "<p style='color:red'>❌ 导入失败:" . htmlspecialchars($e->getMessage()) . "</p>";     } } ?> <!DOCTYPE html> <html> <head><meta charset="UTF-8"></head> <body> <form method="post" enctype="multipart/form-data">     <label>选择 CSV 文件(首行为字段名):</label>     <input type="file" name="myfile" accept=".csv" required><br><br>     <button type="submit">开始导入</button> </form> </body> </html>

? 总结:LOAD DATA LOCAL INFILE 是高效批量导入方案,但成功依赖三要素——服务端文件路径真实存在 + SQL 中路径加单引号 + MySQL 服务端及 PDO 双重启用 local_infile。跳过任一环节均会导致语法错误或拒绝访问。生产环境务必加入文件校验与异常兜底,避免数据污染。

text=ZqhQzanResources