mysql不支持函数级execute权限,仅支持数据库粒度授权;授予insert on mysql.func仅允许注册udf,不影响调用,调用权限由grant execute on db.*控制,且udf可跨库调用。

GRANT INSERT ON mysql.func 是创建UDF的前提,不是调用权限
给用户授予 INSERT 权限到 mysql.func 表,只解决“能否成功执行 CREATE FUNCTION”的问题,和后续“能否调用该函数”完全无关。这是最常被混淆的点——很多人以为授了这个权,就等于放行了函数调用,实际不是。
MySQL 5.7 及以前版本把 UDF 元信息(如函数名、返回类型、SO 文件路径)存进 mysql.func;8.0+ 改为写入 mysql.plugin 表,但逻辑一致:只有能往这张表里插记录的用户,才能完成函数注册。所以 INSERT ON mysql.func 是 DDL 操作的必要条件,不是运行时控制开关。
-
GRANT INSERT ON mysql.func TO 'udf_admin'@'localhost';必须配合CREATE ROUTINE权限才有效,单独授INSERT会报错 - 普通业务账号绝不能拥有该权限——它等价于允许用户在数据库内植入任意本地代码
- 即使你回收了这个权限,已注册的 UDF 仍可被调用(只要调用者有对应数据库的
EXECUTE权限)
为什么 REVOKE INSERT ON mysql.func 不影响已有UDF运行
因为 mysql.func 表只是注册登记簿,不是权限检查表。UDF 加载后驻留在内存中,调用时 MySQL 完全不查这张表的内容,只看两件事:plugin 状态是否为 ACTIVE,以及调用者是否拥有函数所在数据库的 EXECUTE 权限。
你可以验证这一点:
- 用
REVOKE INSERT ON mysql.func FROM 'udf_admin'@'localhost';收回权限 - 再执行
SELECT mytools.sys_exec('id');—— 只要mytools库已授EXECUTE,照样成功 -
SELECT * FROM mysql.func;仍能看到记录,但这条记录此时仅用于DROP FUNCTION或SHOW CREATE FUNCTION
真正决定“谁可以调用UDF”的只有 EXECUTE ON database.*
MySQL 不支持 GRANT EXECUTE ON FUNCTION db.udf_name 这种语法,所有尝试都会触发 ERROR 1370 (42000): execute command denied。系统压根不解析函数粒度的授权语句。
唯一生效的控制方式是按库授权:
-
GRANT EXECUTE ON mytools.* TO 'app_user'@'%';→ 该用户可在任何库中调用mytools.sys_exec() -
REVOKE EXECUTE ON mytools.* FROM 'app_user'@'%';→ 立即失去所有该库下 UDF 的调用能力 - 函数注册在哪个库,不影响调用位置——
mytools.udf_foo()可以在production库里直接 SELECT 调用
注意:EXECUTE 权限默认不显式授予,必须手动加;哪怕用户有 SELECT 权限,没 EXECUTE 也调不了 UDF。
安全隔离的实操底线:别共用数据库注册UDF
如果多个业务方都需要不同 UDF,又不想互相调用,不能靠“限制函数名”或“改权限语法”,只能物理隔离:
- 为每组用户建独立库:
udf_billing、udf_analytics、udf_admin - 每个库只注册各自需要的 UDF:
CREATE FUNCTION udf_billing.pay_status ... - 只授对应库的
EXECUTE:GRANT EXECUTE ON udf_billing.* TO 'billing_app'@'%';
这是目前 MySQL 原生机制下最干净、最可控的方案。任何试图绕过库级粒度去模拟函数级权限的做法(比如视图封装、SQL SECURITY INVOKER + 权限叠加),都会引入额外维护成本和隐蔽漏洞面。











