如何在MySQL的存储过程中书写批量的用户权限/授权语句

网上单独关于MySQL用户权限或者存储过程的文章有不少,但是关于在存储过程中如何书写用户授权语句,尤其是批量授权的资料却很少。

为什么笔者会遇到在存储过程中书写批量用户授权语句的需求,是因为笔者在日常开发中承担了一部分简单的运维职责(笔者还是很赞同开发兼运维的思想),其中就包括在公司内部机器中搭建MySQL服务。

搭建MySQL服务有一项内容就是配好用户和权限管理。笔者不是专职的数据库运维,在此就举个自认为还算形象的例子:比如MySQL中会有系统库(MySQL自带的,比如mysql、performance_schema等)、测试库和正式库,那么我们可能会划分出三类MySQL账号:(1)program账号:程序用的账号,能够对正式或测试库crud,对系统库的部分表有select权限;(2)query账号:开发者用的账号,对正式库或者测试库都只有select权限,对系统库的部分表有select权限。(3)alpha账号:开发者用的测试账号,对测试库有root权限(all privileges),对正式库只有select的权限,对系统库的部分表有select权限。

那么对于program、query和alpha账号(以及将来要增加的其他账号),都会有个共同的需求:指定系统库的部分表授予select权限。而这些表的数量不算少,如果每增加一个账号,就要把所有授权语句依照账号改一边再执行一遍,显得有点麻烦,不如写个存储过程,输入用户账号信息,然后就能把固定需要授予select的权限语句都执行一遍。

用户授权语句类似于:

GRANT SELECT ON `mysql`.`time_zone` TO 'query'@'%';

在存储过程中书写多个用户授权语句类似于:

DELIMITER //
CREATE PROCEDURE grantProc()
  BEGIN
    GRANT SELECT ON `performance_schema`.* TO 'query'@'%';
    GRANT SELECT ON `mysql`.`time_zone` TO 'query'@'%';
    GRANT SELECT ON `mysql`.`slow_log` TO 'query'@'%';
    FLUSH PRIVILEGES;
  END
//
DELIMITER ;
CALL grantProc();
DROP PROCEDURE grantProc;

至于存储过程和授权语句写法的具体细节大家可以搜索其他资料,不是本文重点。

可以看到user@host部分是重复的,如果能通过存储过程入参输入user和host信息,就不用每次新增账号都把存储过程的语句改一遍了。

这里麻烦的一点是MySQL的授权语句grant to后面的user和host都是要带单引号的字符串(尤其是host部分,user不带单引号,用字符串也是可以的,至少笔者最后可以通过)。

笔者试过几次,无论是直接传user和host的字符串还是传入带转义单引号的字符串,执行时MySQL都报“Error : Can't find any matching row in the user table”错误,就是说从mysql.user表里找不到该host下的user用户。本质上还是传参无法直接在grant sql中使用。如下所示:

-- 直接输入字符串
DELIMITER //
CREATE PROCEDURE grantProc(IN inUser varchar(255), IN inHost varchar(255))
  BEGIN
    GRANT SELECT ON `mysql`.`time_zone` TO inUser@inHost;
    FLUSH PRIVILEGES;
  END
//
DELIMITER ;
CALL grantProc('query', '%'); 
DROP PROCEDURE grantProc;
-- 输入带转义单引号字符串
DELIMITER //
CREATE PROCEDURE grantProc(IN inUser varchar(255), IN inHost varchar(255))
  BEGIN
    GRANT SELECT ON `mysql`.`time_zone` TO inUser@inHost;
    FLUSH PRIVILEGES;
  END
//
DELIMITER ;
CALL grantProc('\'query\'', '\'%\''); 
DROP PROCEDURE grantProc;

而如果使用concat直接在grant sql中拼接单引号会报语法错误,如下所示:

DELIMITER //
CREATE PROCEDURE grantProc(IN inUser varchar(255), IN inHost varchar(255))
  BEGIN
    GRANT SELECT ON `performance_schema`.* TO concat('\'', inUser, '\'')@concat('\'', inHost, '\'');
    FLUSH PRIVILEGES;

  END
//
DELIMITER ;
CALL grantProc('query', '%');
DROP PROCEDURE grantProc;

后来笔者试了用concat拼接的动态SQL配合预编译和execute才达到目的。直接上最终的存储过程代码(包含了常用的系统库select权限授予):

DELIMITER //
CREATE PROCEDURE grantProc(IN inUser varchar(255), IN inHost varchar(255))
  BEGIN
    set @v_user = concat(inUser,'@\'',inHost,'\'');

    set @grand_s= concat('GRANT SELECT ON `performance_schema`.* TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`time_zone` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`slow_log` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`help_keyword` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`time_zone_name` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`event` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`time_zone_transition_type` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`help_relation` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`func` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`proc` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`time_zone_leap_second` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`help_category` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`time_zone_transition` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`general_log` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    set @grand_s= concat('GRANT SELECT ON `mysql`.`help_topic` TO ', @v_user, ';');
    prepare cmd from @grand_s;
    EXECUTE cmd;

    FLUSH PRIVILEGES;

  END
//
DELIMITER ;
CALL grantProc('query', '%');
DROP PROCEDURE grantProc;

有几点说明:

(1)set, prepare和execute语句重复了很多次,其实可以再写存储过程或者函数简化,只是笔者不愿意在这上面再花时间了。目前开发趋势已经很少用存储过程了,MySQL这种应该只用于存粹的数据存储,不负担任何逻辑才是正道,所以在存储过程上花时间没必要。笔者写这篇文章也只是作为一种笔记。

(2)语句

concat(inUser,'@\'',inHost,'\'');

意思是拼接成query@'%'的字符串。user没有用单引号拼接,最后也能执行成功,但是host如果不加单引号会包语法错误。sql中转义用的是反斜杠\。

(3)授权语句执行完毕后想要生效最后必须加上:FLUSH PRIVILEGES。

(4)存储过程现在已经不常用了,执行完毕 最后drop掉吧,免得留坑。

你可能感兴趣的:(MySQL)