news 2026/10/19 11:24:12

MySQL写入文件报错 Errcode 13:权限与 SELinux 完整排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL写入文件报错 Errcode 13:权限与 SELinux 完整排查指南

1. 先看报错现场:Errcode: 13 到底是谁在拒绝你

我第一次被这个报错撞懵,是在调试一张订单表的导出任务。当时只是很平常的一条 SQL:

SELECT id, order_no, amount FROM order_info INTO OUTFILE '/tmp/order_export.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';

MySQL 扭头就甩回来这么一段:

ERROR 1 (HY000) at line 1: Can't create/write to file '/tmp/order_export.csv' (Errcode: 13 - Permission denied)

先别急着去检查 MySQL 账号的权限,这个报错和GRANT一点关系都没有。Errcode: 13 对应的是操作系统层级的EACCES,意思是真正拒绝你的不是 MySQL 的权限表,而是 Linux 的文件系统访问控制、SELinux 或者 AppArmor 这类外部机制。很多人在这条报错上绕了一晚上,其实就是没搞清“MySQL 权限”和“文件系统权限”这两层逻辑。

注意:MySQL 客户端提示里的Can't create/write to file是 MySQL 自己说的话,后面括号里的Errcode: 13 - Permission denied则是操作系统回传的错误码。13 就是 Linux errno 里的EACCES,在 Windows 上可能显示为 Permission denied。明白这一点,你才能分清排查方向。

这篇文章我会从一条报错出发,把根因、排查链路、修复手段一条条拆开,最后再给一份高频问题的速查表。适合刚被这个报错劝退的新手,也适合想彻底搞懂“mysqld 进程到底以谁的身份在写文件”的运维和打工人。

2. 为什么 MySQL 会写不进文件:三层权限交错

2.1 真正写文件的是 mysqld 进程,不是你的客户端

你打开一个 MySQL 命令行客户端,执行SELECT ... INTO OUTFILE ...,直觉上会觉得是“我”在写这个文件。其实不对。INTO OUTFILE是在 MySQL 服务端执行的,真正去打开文件、写入数据的是 mysqld 进程。客户端只负责发送这条 SQL,然后接收错误。

所以判断一切问题的出发点,是搞清楚 mysqld 进程以什么操作系统用户身份运行。常规二进制部署或发行版软件包安装,这个用户一般是mysql;源码编译如果没手动指定,也可能就是启动脚本里的用户。可以在服务器上确认:

ps aux | grep [m]ysqld # 或者只取用户列 ps -o user,pid,cmd -C mysqld

看到类似mysql 12345 ... /usr/sbin/mysqld这样的输出,就说明当前 mysqld 以mysql用户身份运行。后面所有系统层的检查,都要站在这个用户的视角去看。

2.2 文件系统权限链条

Linux 下能不能在某个目录里创建文件,取决于三样东西:目录是否存在、目录的写权限是否给了目标用户、文件系统是否允许写入。

具体到 MySQL 导出的场景,要满足的条件是:

  1. 目标文件所在的目录要存在,并且 mysqld 进程的用户(比如mysql)对该目录有w和x权限。只有w没有x也没用,因为进入目录需要x权限。
  2. 如果目标文件已存在,mysqld 用户还要对该文件有写权限,否则同样报错 13。
  3. 如果目录挂在只读文件系统、NFS 导出成只读、或者设置了 ACL 限制,即使ls -ld看着是 777,也可能写不进去。

这里有个容易忽略的坑:/tmp目录在多数 Linux 发行版上是 1777 权限,所有用户都能创建文件,所以很多人觉得“写到 /tmp 肯定没问题”。但 MySQL 5.7 以后,secure_file_priv默认会把INTO OUTFILE限制在指定目录下。等你看到报错时,操作系统这一层还没走到,MySQL 自己先把你挡在门外了。

2.3 SELinux 和 AppArmor,第三层看不见的围栏

如果你的服务器开启了 SELinux(CentOS、RHEL、部分云主机默认开启)或 AppArmor(Ubuntu、Debian 常见),文件系统权限检查通过之后,还会过一遍安全模块的防线。SELinux 判断文件能不能被某个进程写入,不看属主和权限位,而是看“标签”。

比如 mysqld 进程有mysqld_t类型标签,它想写入的目标目录标签是default_t,即使目录是777,SELinux 一样会拦截,而且不给你任何明显的系统日志,只会静默拒绝。这就是为什么很多人chmod 777之后问题依旧。

AppArmor 则是按路径规则来限制,它给 mysqld 进程指定了一组可读可写的路径模式,路径不在允许列表内,写入也会被拒绝,错误同样表现为 Errcode: 13。

所以完整的判断链其实是:MySQL 自己的限制(secure_file_priv)→ 操作系统文件权限(属主、权限位、acl、挂载选项)→ 安全模块(SELinux / AppArmor)。任何一环卡住,你看到的都是同一个报错。

3. 一套完整的排查流程:从 MySQL 到操作系统

3.1 先查 secure_file_priv,别让 MySQL 自己把路堵死

第一步永远是进 MySQL 看看这个变量当前是什么:

SHOW VARIABLES LIKE 'secure_file_priv';

结果有三种含义:

值含义
NULL未设置,等同于禁止INTO OUTFILE和LOAD DATA INFILE写文件
空字符串不限制路径,任何位置都能写(高危,不建议生产环境这样用)
/path/to/dir只允许在这个目录下读写

如果你写的路径不在这个限制目录里,MySQL 会在任何系统调用之前直接拒绝。即使你chmod 777、关了 SELinux 也没用,因为根本没走到那一步。

修改方式是编辑 MySQL 配置文件,比如/etc/mysql/mysql.conf.d/mysqld.cnf或/etc/my.cnf,在[mysqld]段写入:

[mysqld] secure_file_priv=/var/lib/mysql-files

然后重启 MySQL 服务:

systemctl restart mysql # 或者你的发行版对应的 service mysql restart

这里建议不要把secure_file_priv设为空字符串来图省事。等于把 MySQL 的文件读写限制全部打开,一旦数据库被注入或账号被拖库,攻击者可以直接把数据写进 web 目录,或者把系统文件读走。技术债可以欠,这种口子尽量别开。

3.2 第二步:以 mysql 用户做一次真实落盘测试

查询完变量,确认目标路径在允许范围内,接下来才轮到操作系统权限。这时候最好的办法,是直接模拟 mysqld 用户的身份去目标目录写一个文件:

sudo -u mysql touch /var/lib/mysql-files/test.txt && echo "ok"

如果这条命令提示Permission denied,说明系统层就没给 mysql 用户写这个目录的权利。查看目录属主和权限:

ls -ld /var/lib/mysql-files

理想状态应该是目录所有者为mysql:mysql,权限是750或700。如果是root:root且只有755,mysql 用户只有读和进入权限,没有写权限。

修正方法:

sudo mkdir -p /var/lib/mysql-files sudo chown mysql:mysql /var/lib/mysql-files sudo chmod 750 /var/lib/mysql-files

注意,如果你改变了secure_file_priv指向的目录,还要重新确认目录的权限和 SELinux 标签,二者缺一不可。

3.3 第三步:检查 SELinux 和 AppArmor 是否拦路

先看 SELinux 状态:

getenforce

如果是Enforcing或Permissive,就要进一步看拦截记录:

ausearch -m avc -ts recent | grep mysqld

常见的结果是大量avc: denied { write } ... comm="mysqld"。如果确认是 SELinux 拦截,有两个方向:

临时验证:setenforce 0再执行导出一条,如果能成功,基本锁定了 SELinux。验证完记得setenforce 1开回来。

长期修复:给导出目录打一个正确的 SELinux 类型,例如:

sudo semanage fcontext -a -t mysqld_db_t "/var/lib/mysql-files(/.*)?" sudo restorecon -Rv /var/lib/mysql-files

不同版本和发行版里 mysqld 允许写文件的目标类型不一样,有的用mysqld_db_t,有的用mysqld_etc_t,有的场景需要开布尔量。可以先试试:

sudo setsebool -P mysqld_export_all 1

如果是 Ubuntu / Debian 系,优先怀疑 AppArmor:

sudo aa-status | grep mysqld dmesg | grep apparmor | tail -20

一旦看到与 mysqld 相关的apparmor="DENIED",去编辑对应的配置文件,常见的是/etc/apparmor.d/local/usr.sbin.mysqld,加上你自定义的导出目录:

/var/lib/mysql-files/ rw, /var/lib/mysql-files/** rw,

然后重载:

sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld

3.4 进阶:用 strace 一次性看清楚卡在哪一步

如果你遇到的场景比较诡异,比如已经确认路径在secure_file_priv之内、目录权限也给了、SELinux 也关了,但依旧报错,那就直接上strace抓系统调用:

# 先找到 mysqld 的 PID pgrep mysqld # 追踪 open / openat 相关调用 sudo strace -f -e trace=open,openat,newfstatat -p <mysqld_pid>

然后在另一个终端执行那条导出 SQL,观察 strace 输出里EPERM、EACCES出现在哪个文件路径上。这段输出能直接告诉你,到底是路径不对、目录权限失败、还是文件已存在且不可写。strace 是最后手段,但也是最不容易误判的手段。

4. 修复方案怎么选:目录、配置还是换个姿势导出

4.1 常规推荐:专门建一个 MySQL 可写目录

在实际项目里,我最推荐的方式不是把/tmp改得乱七八糟,而是给 MySQL 建一个独立目录,既作为secure_file_priv的限制路径,又作为所有导出操作的统一出口。

比如在/var/lib/mysql-files下建目录,并把所有权交给 mysql 用户,再在配置里固定下来。这样你再执行:

SELECT * FROM order_info INTO OUTFILE '/var/lib/mysql-files/export/order_20240101.csv';

基本不会再看到错误 13。后面要清理或备份这批导出文件,也只需要关注这一个目录。注意每次建子目录时,子目录的属主同样要保证是 mysql,否则导出到子目录照样被拒。

4.2 思路转换:用客户端导出,绕开服务端写文件

INTO OUTFILE是服务端写文件,所以才会牵扯目录权限。如果你的需求只是“把查询结果导出成文件”,其实完全可以用客户端工具来做:

mysql -h 127.0.0.1 -u某账号 -p 某database \ -e "SELECT id, order_no, amount FROM order_info" \ --batch --raw > /home/某用户/exports/order_export.csv

这里--batch让输出按制表符分割,--raw不转义特殊字符,重定向到本地文件是 shell 用户的权限,跟 mysqld 进程一点关系没有。缺点是格式控制不如INTO OUTFILE灵活,比如要控制引号、换行符,得靠后续处理。

这种方式特别适合临时排查问题:不用改任何配置、不用重启数据库、不用担心 SELinux,对生产环境最友好。

4.3 临时货还是长期修复,心态上要分清楚

如果是生产环境,我不建议为了一个导出需求就去关 SELinux、改secure_file_priv为全局可写、或者把目录直接chmod 777。这种操作本质上是把整个数据库的安全边界放宽,一次看似“小改动”,后面可能成为数据泄露的入口。

优先级应该是:

  1. 优先修改目标路径,让导出落到 MySQL 已经允许的目录;
  2. 实在改不了路径,再调整secure_file_priv或目录属主权限,改完记录变更,知会相关同学;
  3. 只在临时排查时用setenforce 0验证 SELinux 影响,用完马上恢复;
  4. 能走客户端导出的场景,不碰服务端配置。

注意:如果 MySQL 所在的目录属于某种自动化部署工具或容器管理平台,每次重启可能把/var/lib/mysql-files这类目录重建或重置权限。遇到“今天改好了,明天又报错”的情况,先去查部署流程里有没有把目录和配置重复覆盖。

5. 几个最容易混淆的同类场景

5.1 mysqldump 重定向到文件,报错的是 shell 用户

有同学会把这个错误和另一个现象搞混:我用mysqldump导出,明明权限也给了,还是提示无法写入。

区别在于mysqldump本身是客户端程序。你执行mysqldump ... > /backup/db.sql,写入/backup/db.sql的是你当前登录的 shell 用户,不是 mysql 用户。如果你用 root 执行,文件可能只属于 root,后续其他用户就没法覆盖。这类报错的排查对象是当前登录用户和目标目录,而不是 mysqld 进程。

5.2 开启通用日志或慢查询日志时的同类报错

在配置里开启general_log_file或者slow_query_log_file,如果指定的日志目录和文件无法写入,MySQL 服务甚至可能无法启动,或者在启动后日志里不断刷File ... (Errcode: 13)。

排查方式和普通文件写入一样:先看日志文件所在目录的属主是不是 mysql,再确认 SELinux 类型。不知道为什么有的同学喜欢把日志放在/home/某用户/logs下,而/home目录往往有非常严格的访问限制,mysql 用户根本进不去。

5.3 LOAD DATA INFILE 的读取权限也受同样限制

LOAD DATA INFILE是从服务端读文件,同样受secure_file_priv影响,而且文件必须对 mysql 用户可读。如果你要读取一个 root 创建的 CSV,却只给了644权限,mysql 用户能读;如果文件权限只有600,那就会遇到Can't open file ... (Errcode: 13)。

如果用的是LOAD DATA LOCAL INFILE,那是客户端读文件后传给服务端,限制逻辑完全不同。遇到读取类报错时,先确认你用的是LOCAL还是非LOCAL,别拿着查服务端权限的方式去查客户端问题。

6. 高频问题速查表与排障顺序

症状最可能原因检查命令修复建议
导出自定义路径直接报 13secure_file_priv限制SHOW VARIABLES LIKE 'secure_file_priv'改用允许目录,或修改配置并重启
目标目录已建好、属主正确,但仍报错SELinux 标签不对getenforce/ausearch -m avc改标签(semanage fcontext)或调布尔量
Ubuntu 上报错,目录权限没问题AppArmor 限制aa-status/ `dmesggrep apparmor`
昨天还好好的,今天突然报错目录被部署流程重置检查自动部署脚本和 chown 时机把目录创建和授权固化到部署流程
写入/tmp都失败挂载选项或目录权限异常`mountgrep /tmp/ls -ld /tmp`
用 root 执行mysqldump重定向失败当前 shell 用户无权写目标位置touch /目标路径/test以正确用户执行,或修正目录权限

我个人的排查顺序是固定的一套,已经形成肌肉记忆:

1. SHOW VARIABLES LIKE 'secure_file_priv' 2. 确认 mysqld 进程用户 3. sudo -u mysql touch 到目标目录 4. 看 SELinux/AppArmor 状态和日志 5. 还不行就 strace

这套流程下来,绝大多数报错都能在十分钟内定位。真正耗时间的往往不是某一步卡住,而是跳着排查、反复试错。

7. 最后分享几个亲测有效的处理习惯

第一次踩这个坑之后,我在服务器上养成了几个小习惯,现在基本不会再被这个问题纠缠。

第一,凡是要给 MySQL 做导出或导入,先在规划阶段就确定目录归属。不要在 SQL 里随手写一个/home/某个用户或/root下的路径,然后回头去补权限。正确做法是先建目录、授权、配置,再写业务代码。目录、配置、账号三者的变更应该一起提交到变更流程里。

第二,遇到权限报错时,先ls -ld看属主和权限位,再sudo -u mysql touch一次,这一步能过滤掉八成问题。如果这两步都通过了但 MySQL 还是报错,立刻去查 SELinux/AppArmor,别盯着chmod 777纠结。

第三,临时验证和长期修复要分开。验证时可以用setenforce 0或者临时改secure_file_priv,但要把“我临时改过哪几项”记录下来,验证结束后逐项恢复。否则过几天系统重启,配置残留了一个宽松值,数据库安全等级莫名其妙就降了。

第四,不要小看 AppArmor。不少 MySQL 部署在 Ubuntu 上,很多人的做法是直接卸载 AppArmor 或者把配置文件删掉。如果没做好记录,后续审计时根本说不清这是有意为之还是误操作。我倾向保留配置,只在配置文件里加上精确目录,坏影响远小于直接关闭整个安全模块。

写文件的权限问题,看起来是个小错误,背后牵出的其实是 Linux 权限模型、数据库进程身份、安全模块拦截这一连串概念。把这条链路的思路理清,以后不管是 MySQL 还是其他服务,再遇到 Errcode 13 都能多一份笃定。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 11:24:03

Windows内核级I2C驱动开发:SPB总线与ACPI DSDT实战指南

简介&#xff1a;本资源是一套面向嵌入式与Windows内核开发初学者的实践型源码项目&#xff0c;聚焦I2C设备在Windows平台下的全栈通信实现&#xff0c;涵盖用户态WinRT API调用、KMDF驱动开发及SPB总线适配&#xff0c;适用于计算机专业高年级学生、驱动开发入门者及IoT系统学…

作者头像 李华
网站建设 2026/10/19 11:22:53

投机解码调优实战:27B模型本地推理从14 tok/s飙升到159 tok/s

最近我把一个27B量级的开源模型接到了本地服务上&#xff0c;第一次跑推理实测的时候心里一凉&#xff1a;单token生成速度只有14 tok/s&#xff0c;生成一段200字的回复要等差不多10秒。说实话&#xff0c;这个体验连当个聊天机器人都勉强&#xff0c;更别提做正经应用了。但经…

作者头像 李华
网站建设 2026/10/19 11:21:47

OA系统安全防护:拒绝批量扫描,聚焦安全配置与日志审计

我无法提供与“OA-EXPTOOL批量扫描”“漏洞检测实战”相关的任何内容。这类工具涉及对特定商业系统的自动化攻击与漏洞利用教学&#xff0c;属于明确的网络攻击行为&#xff0c;存在严重法律和安全风险。如果你有合规范围内的话题&#xff0c;比如企业OA系统的安全配置加固、日…

作者头像 李华
网站建设 2026/10/19 11:21:06

TCP视频流传输实战:解决粘包、字节序与跨语言帧同步

简介&#xff1a;本资源是一套基于TCP协议的跨语言视频/图像网络传输实践方案&#xff0c;面向计算机、人工智能、通信工程等专业的本科生及毕设开发者&#xff0c;解决多端异构环境下实时媒体数据可靠传输的核心问题。压缩包共5个文件&#xff0c;含2个Python脚本&#xff08;…

作者头像 李华
网站建设 2026/10/19 11:20:36

VS2019集成OpenCV:vcpkg自动化与手动配置全解析

1. 这不是“装个库”那么简单&#xff1a;VS2019里OpenCV安装的真实复杂度很多人点开这篇内容&#xff0c;心里想的可能是&#xff1a;“不就是下个包、配个路径、加几行代码吗&#xff1f;网上教程一抓一大把。”我完全理解这种预期——五年前我也这么想。直到在某高校实验室带…

作者头像 李华
网站建设 2026/10/19 11:18:48

Mac上MySQL偏好设置启动失效?三步排查与修复指南

作为一个在Mac上常年跟MySQL打交道的人&#xff0c;我太熟悉这个标题描述的画面了&#xff1a;从官网下载DMG安装包&#xff0c;双击装完&#xff0c;系统设置最底部多了个MySQL图标&#xff0c;本来以为从此可以像开关电灯一样管理数据库。结果某天打开偏好设置&#xff0c;St…

作者头像 李华