零、问题背景
最近想换房,为了方便自己对比感兴趣的房子,因此决定将目标房源的基本信息放在表里,特别是要一目了然的看到众多房子的各种图纸和照片,因此决定要在Mysql8.0.34数据库中以二进制形式保存图片(抛开合理性和性能暂且不谈,这里只说技术实现),许多帖子说使用load_file函数来实现,但我按照其他人的方法实操时一直返回NULL,还好历经千辛万苦终于解决了,现将过程与思路加以总结,存档留念。
一、准备工作
1.1、服务器与数据库版本
我是用 rpm 方式安装的 MySQL 8.0.23 版本,服务器操作系统是Centos 7.9 虚拟机资源 2C 4G 40G
1.2、表结构
下图是我的表结构,其中有几个字段是 mediumblob类型,用来存放大小约为200k以内的图片。
1.3、图片保存位置
为了便于后续业务扩展,也出于数据隔离的需要,我用服务器上自定义了 /data/pic/house 路径作为当前业务存储图片的根目录。并在其下面创建了 housepic 和 roomtype两个路径。
二、问题分步展示
2.1、问题展示
我将一张名为 h_1.jpg 的图片放到 /data/pic/house/roomtype 下,然后使用如下语句尝试查看图片时没能返回预期图片内容,而是返回了NULL。
sql
select load_file('/data/pic/house/roomtype/h_1.jpg');
2.2、原因及secure_file_priv介绍
load_file 函数返回为 null 的原因,根源在于 MySQL 配置文件中的 "secure_file_priv" 属性取值与load_file 函数使用的路径不一。在具体解决问题之前,应该先了解这一属性的作用和取值范围是什么。
2.2.1、secure_file_priv介绍
该属性有三类取值,分别为 "NULL"、空值、具体路径字符串。不同取值的含义如下表所示
|-------------|----------------------|
| 取值 | 含义 |
| NULL | 不允许读写任何路径下的文件 |
| '' 或 没有值 | 允许读写任意路径下的文件 |
| 字符串(如 /tmp) | 允许读写当前路径(如 /tmp)下的文件 |
2.2.2、secure_file_priv 查看
在MySQL任意一种客户端中输入如下命令查看。secure_file_priv的默认取值如下图所示,为 "/var/lib/mysql-files/"。说明当前load_file 函数只能操作 "/var/lib/mysql-files/" 路径下的文件。
sql
show variables like '%secure%';
2.2.3、secure_file_priv 修改
通过上面的分析已经知道了问题的原因,下面就是在MySQL的配置文件中修改这一参数的取值。因为我的MySQL是rpm安装版,所以配置文件的名称与路径为: /etc/my.cnf (其他版本操作系统下的MySQL的配置文件位置与名称请自行确认)。同时我希望 MySQL 不限制文件路径,因此 secure_file_priv 取值及展示如下所示:
cs
#关闭mysql的文件读写路径限制,取值为空
secure-file-priv= ''
需要注意的一点就是,配置文件中应写成 "secure-file-priv" 而非"secure_file_priv"的形式,切切!
2.3、效果与终极解决方法
如果逐位按照上面的方法作了修改,大概率是依然不成功。原因在于没有关闭selinux 。我之前就在selinux 这个坑中挣扎了好久才爬出来。下面就简单聊聊 selinux 是啥,以及为什么会对这个参数有这么大的影响。
2.3.1、selinux是什么、能做什么
它是"安全增强型 Linux(Security-Enhanced Linux)"的简称,它是 Linux 的一个安全子系统。
它能对linux系统中,包括root在内的所有用户的行为做管制。它能通过一系列的配置实现 "允许 X 目标,对 Z 对象/资源,做 Y行为 " (May <subject> do <action> to <object/source>)的访问控制。其中,X多指进程;Y多指X访问的资源,如端口、文件等;Z多指访问或操作规则。
它有三种工作模式,如下表所示:
|------------|-----------------------------|
| 模式名称 | 作用 |
| enforcing | 强制模式。阻止并在日志记录违反SELinux规则的行为 |
| permissive | 宽容模式。仅在日志记录违反SELinux规则的行为 |
| disabled | 关闭模式。关闭SELinux |
selinux 是一套相当复杂的子系统,里面有庞大的配置项需要了解,在此不做进一步的解释,只需要知道它会对 MySQL 的关键行为做校验与限制即可。除非是专门负责安全和服务器运维的工程师需要深入了解selinux各种规则之外,一般的开发人员在遇到linux 的一些校验失败的情况时,可以想到有这样一个东西存在,并且知道通过关闭selinux可以解决大部分的安全校验问题即可。
2.3.2、selinux 的关闭与开启
selinux 的关闭分为临时关闭和永久关闭,下图为临时关闭方式(即设为宽容模式)
下面为永久关闭方法
cpp
# 修改 /etc/selinux/config 文件中的 SELINUX=disabled
[root@MySQL01 ~]# vim /etc/selinux/config
# 修改 /etc/sysconfig/selinux 文件中的 SELINUX=disabled
[root@MySQL01 ~]# vim /etc/sysconfig/selinux
2.4、最终效果
在MySQL 的 secure_file_priv参数设置为理想的值,并且关闭了 selinux 之后,发现可以正常读取到图片了,如下图所示
2.5、补充说明
存放图片的路径,建议放到 mysql 属组,且赋予 对应的权限。如下图所示
三、说在最后
9.1 如何查看centos 版本
bash
cat /etc/redhat-release
9.2、如何查看MySQL版本
sql
select VERSION();
9.3、load_file 函数的作用与效率
它在 MySQL 中的主要作用是读取本地或远程的文件。文件种类涵盖文本、二进制、图片、视频等。因这样的读取方式不够安全,因此建议在受信的环境中使用。
9.4、MySQL 几种操作图片的思路和对比
可以通过load_file 函数将图片记录到字段中,也可以将图片所在的路径记录到字段中。前者存在安全隐患,后者无法在MySQL中直接看到图片内容。因此对图片的处理方式应根据业务需求等因素综合考虑后选择。