Django自定义命令执行LOAD DATA INFILE导入MySQL报1064错误
报错排查思路
这个1064语法错误核心是SQL拼接逻辑存在硬伤,按以下优先级排查即可:
- 直接触发报错的问题:
LOAD DATA INFILE '%s'中手动给占位符加了单引号。Django的cursor参数化执行逻辑会自动为字符串类型参数添加边界引号、转义特殊字符,手动加单引号会最终生成LOAD DATA INFILE ''/your/path/file.txt''这类双重引号的非法SQL,报错指向path位置就是这个原因。 - 第二处语法错误:库名、表名属于SQL标识符,不能通过
%s占位符传参。MySQL预处理语法仅支持值类型的参数占位,标识符(库名、表名、字段名)走%s传参会被自动转义为带引号的字符串,最终生成INTO TABLE 'your_db.your_table'的非法语法,表名会被解析成字符串值而非合法标识符。 - 隐藏逻辑bug:glob匹配规则
*[!.sql]写法错误。方括号内的字符集是单字符匹配规则,这个写法实际是匹配文件名最后一个字符不是.、s、q、l的文件,完全实现不了「排除.sql后缀文件」的需求,很容易漏扫目标txt文件、或者把非目标文件纳入导入范围。 - 权限适配问题:如果使用MySQL 8.x默认安全配置,读取客户端本地文件需要用
LOAD DATA LOCAL INFILE而非LOAD DATA INFILE——后者默认要求文件存储在MySQL服务端宿主机器上且匹配系统权限,手动在MySQL终端执行成功一般是因为终端客户端默认开启了local_infile支持,代码里不加LOCAL关键字会被MySQL拒绝读取本地文件。
修正后的Django自定义命令实现
修复上述所有问题,补充基础校验和异常提示,代码如下:
import glob import os from django.core.management.base import BaseCommand from django.db import connections from django.conf import settings class Command(BaseCommand): help = '批量导入指定目录下的txt文件到MySQL数据库' def add_arguments(self, parser): parser.add_argument('-p', required=True, type=str, help='待导入文件所在目录路径') def handle(self, *args, **options): self.stdout.write("\nStarting import task...") target_dir = options["p"] db_name = settings.DATABASES['wk']['NAME'] # 修正glob规则:直接匹配目录下所有txt后缀文件,天然排除.sql等其他格式文件 file_list = glob.glob(os.path.join(target_dir, '*.txt')) if not file_list: self.stdout.write(self.style.WARNING("指定目录下未找到待导入的txt文件,任务退出")) return with connections['wk'].cursor() as cursor: # 开启会话级本地文件读取权限,适配客户端侧文件导入场景 cursor.execute("SET SESSION local_infile = 1") for file_path in file_list: file_name = os.path.basename(file_path) # 提取无后缀文件名作为表名,统一转小写 table_name_raw = os.path.splitext(file_name)[0].lower() # 校验表名合法性,避免特殊字符引发语法错误或注入风险 if not table_name_raw.isidentifier(): self.stderr.write(self.style.ERROR(f"跳过非法文件:{file_name},文件名包含不符合MySQL表名规则的字符")) continue # 给库名、表名加反引号,兼容保留字、特殊命名场景 full_table_name = f"`{db_name}`.`{table_name_raw}`" try: # 文件路径作为值类型走参数化传参,不加手动引号;表名完成校验后直接拼接 # 如有字段分隔符、换行符、跳过表头的需求,直接在表名后补充 FIELDS TERMINATED BY、LINES TERMINATED BY、IGNORE 1 LINES 等配置即可,和终端执行语法完全一致 sql = "LOAD DATA LOCAL INFILE %s INTO TABLE %s" cursor.execute(sql, [file_path, full_table_name]) self.stdout.write(self.style.SUCCESS(f"成功导入文件:{file_name} -> 表{full_table_name}")) except Exception as e: self.stderr.write(self.style.ERROR(f"导入文件{file_name}失败:{str(e)}")) self.stdout.write(self.style.SUCCESS("所有文件导入任务执行完成"))
如果你手动在终端执行LOAD DATA时配置过特殊字段分隔符、行分隔符、忽略首行、字段转义规则,直接把对应参数补到LOAD DATA语句的表名后即可,和终端执行的语法保持完全一致就不会出错。
内容的提问来源于stack exchange,提问作者af3ld
相关产品推荐
相关产品推荐

