PHP文件函数(filesize、readfile、unlink)无法读取MySQL生成的文件
问题
在Apache环境下使用PHP 7.4.33与MySQL,执行以下查询语句将数据导出到临时文件$tmpfile:
SELECT [blah] from [blah] INTO OUTFILE $tmpfile FIELDS TERMINATED BY ',' FIELDS TERMINATED BY '\n';
随后通过以下PHP代码尝试下载该文件:
header("Pragma: public"); header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Cache-Control: private",false); header("Content-Type: text/csv"); header("Content-Disposition: attachment; filename=\"contacts.csv\";"); header("Content-Transfer-Encoding: binary"); header("Content-Length: ".filesize( $tmpfile )); readfile( $tmpfile ); unlink( $tmpfile );
但filesize()、readfile()、unlink()均报错,下载的文件为空,报错示例:
PHP Warning: filesize(): stat failed for /tmp/contacts-25178.csv in /[blah]/index.php
不过通过命令行执行ls -l /tmp/contacts-25178.csv能看到文件存在:
-rw-r--r-- 1 mysql mysql 151253 Jan 24 21:29 /tmp/contacts-25178.csv
请问该问题的原因是什么?
原因分析
进程属主不匹配
从命令行输出可知,导出文件的属主和属组均为mysql用户,而Apache/PHP进程通常以apache(部分系统为www-data)用户运行。尽管文件权限设置为-rw-r--r--(其他用户拥有读权限),但跨用户的文件访问会被系统安全机制限制,导致PHP进程无法读取该文件。SELinux安全策略拦截
若系统开启了SELinux(常见于CentOS、RHEL等发行版),即便文件权限符合要求,SELinux的上下文标签机制也会阻止Apache进程访问MySQL生成的文件。MySQL在/tmp目录下创建的文件带有mysql_db_t类安全标签,而Apache进程仅能访问带有httpd_sys_content_t或httpd_tmp_t标签的文件,两者标签不匹配会直接触发访问拦截。SQL语句语法错误(次要)
你的SQL查询中重复使用了FIELDS TERMINATED BY,正确写法应为FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'。该错误仅会导致导出的CSV格式异常,不会直接引发PHP无法读取文件的问题,但建议修正以保证导出数据格式正确。
内容的提问来源于stack exchange,提问作者Robert S

