MySQL存儲過程

項目要求根據天進行分表,可是DBA要求建表要控制權限。表結構固定,所以要求不直接給建表權限,只能經過存儲過程來建表。java

思路:將建表的語句作成存儲過程,而後在須要建表是,調用存儲過程建立分表。mysql

建立存儲過程

-- procedure.sql
DELIMITER $$

DROP PROCEDURE IF EXISTS `new_dove_table`;$$
CREATE DEFINER=`root`@`%` PROCEDURE `new_dove_table`(IN tname varchar(200), OUT res int)
BEGIN
SET @sqlcmd = CONCAT('CREATE TABLE IF NOT EXISTS `', tname, '` (`id` bigint(20) NOT NULL, `content` mediumtext COLLATE utf8mb4_unicode_ci, `time` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `time_idx` (`time`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');
PREPARE stmt FROM @sqlcmd;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET res=1;
END$$

DELIMITER ;

tname是傳遞的表名稱,IN表明是傳入的參數,類型爲字符串。 res是返回的應答,OUT表明返回的結構,類型爲數字。sql

EXECUTE stmt; 執行建表SQL數據庫

登陸mysql: mysql -h127.0.0.1 -P3306 -uroot -pless

msyql> source procedure.sql

java調用存儲過程

調用前,先建立test的帳戶,用戶測試 mysql -uroot -S tmp/mysql.sokcide

mysql> grant select,insert,update,delete,execute on test.* to 'test'@'%' identified by '123456';
mysql> FLUSH PRIVILEGES;

使用JDBC進行調用,可使用C3P0庫。測試

public class ProcedureTest {
    static Connection conn = null;  

    public static void main(String[] args) {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            //創建JDBC鏈接
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","test","123456");   
        } catch (Exception e) {  
            System.out.println("數據庫異常!");  
            e.printStackTrace();  
        }
        
        callProcedure();
    } 
            
    public static void callProcedure() {
        String sqlx = "{CALL `new_dove_table`(?,?)}";  
        try {  
            //建立一個JDBC聲明
            CallableStatement stmt = conn.prepareCall(sqlx);
            //設置傳入的參數
            stmt.setString(1, "test_table"); 
            //註冊執行結果
            stmt.registerOutParameter(2, Types.INTEGER);
            //執行存儲過程
            stmt.executeUpdate(); 
            
            System.out.println(" test mysql procedure : " + stmt.getInt(2));
        } catch (SQLException e) {  
            e.printStackTrace();  
        } finally {  
            //預防性關閉鏈接(避免異常發生時在try語句塊關閉鏈接沒有執行)  
            try {  
                if (conn != null) conn.close();  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
        }  
    }
}

能夠執行程序後,查看錶是否建立成功。ui

遇到的問題

在實際過程當中遇到如下報錯:url

java.sql.SQLException: The user specified as a definer ('root'@'%') does not exist
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3593)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3525)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1986)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2140)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2626)
	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2111)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2407)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2325)
	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2310)
	at com.mysql.jdbc.CallableStatement.executeUpdate(CallableStatement.java:973)
	at jdbc.mysql.ProcedureTest.callProcedure(ProcedureTest.java:37)
	at jdbc.mysql.ProcedureTest.main(ProcedureTest.java:24)

分析多是權限問題,查看下procedure信息code

mysql> show procedure status\G;
*************************** 1. row ***************************
                  Db: test
                Name: new_dove_table
                Type: PROCEDURE
             Definer: root@%
            Modified: 2015-12-15 20:23:30
             Created: 2015-12-15 20:11:49
       Security_type: DEFINER
             Comment: 
character_set_client: latin1
collation_connection: latin1_swedish_ci
  Database Collation: latin1_swedish_ci
6 rows in set (0.00 sec)

ERROR: 
No query specified

mysql>

security_type爲definer,也即只有建立者纔有權限。修改權限

mysql> ALTER PROCEDURE `new_dove_table` SQL SECURITY INVOKER;
Query OK, 0 rows affected (0.00 sec)

mysql>

執行完畢後,能夠正常調用和建立表。

上線問題

上線後,遇到一個報錯,致使沒法建立表

[2015-12-15 17:24:57] ERROR ~ checkQueueExist exception: User does not have access to metadata required to determine stored procedure parameter types. If rights can not be granted, configure connection with "no
AccessToProcedureBodies=true" to have driver generate parameters that represent INOUT strings irregardless of actual parameter types.
java.sql.SQLException: User does not have access to metadata required to determine stored procedure parameter types. If rights can not be granted, configure connection with "noAccessToProcedureBodies=true" to h
ave driver generate parameters that represent INOUT strings irregardless of actual parameter types.
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:987)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:982)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:927)
        at com.mysql.jdbc.DatabaseMetaData.getCallStmtParameterTypes(DatabaseMetaData.java:1630)
        at com.mysql.jdbc.DatabaseMetaData.getProcedureOrFunctionColumns(DatabaseMetaData.java:4246)
        at com.mysql.jdbc.DatabaseMetaData.getProcedureColumns(DatabaseMetaData.java:4087)
        at com.mysql.jdbc.CallableStatement.determineParameterTypes(CallableStatement.java:845)
        at com.mysql.jdbc.CallableStatement.<init>(CallableStatement.java:626)
        at com.mysql.jdbc.JDBC4CallableStatement.<init>(JDBC4CallableStatement.java:47)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:407)
        at com.mysql.jdbc.CallableStatement.getInstance(CallableStatement.java:522)
        at com.mysql.jdbc.ConnectionImpl.parseCallableStatement(ConnectionImpl.java:4008)
        at com.mysql.jdbc.ConnectionImpl.prepareCall(ConnectionImpl.java:4092)
        at com.mysql.jdbc.ConnectionImpl.prepareCall(ConnectionImpl.java:4066)
        at helper.mysql.MysqlHelper.executeNewQueueTableProcedure(MysqlHelper.java:53)
        at storage.impl.mysql.MySQLDataStorage.createQueueTable(MySQLDataStorage.java:193)
        at storage.impl.db.DBDataStorage.checkQueueExist(DBDataStorage.java:49)
        at server.SubscriberManager.checkQueueExist(SubscriberManager.java:142)
        at command.impl.PullCommand.run(PullCommand.java:103)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)

經過分析異常仍是權限問題,可是爲什麼兩次的權限問題不同? 咱們就在線上mysql url添加了 noAccessToProcedureBodies=true 參數,可是依然存在此報錯。 雖然執行了 ALTER PROCEDURE new_dove_table SQL SECURITY INVOKER; 報錯依然存在。

mysql> show procedure status\G;
*************************** 1. row ***************************
                  Db: test
                Name: new_dove_table
                Type: PROCEDURE
             Definer: root@localhost
            Modified: 2015-12-15 20:23:30
             Created: 2015-12-15 20:11:49
       Security_type: DEFINER
             Comment: 
character_set_client: latin1
collation_connection: latin1_swedish_ci
  Database Collation: latin1_swedish_ci
6 rows in set (0.00 sec)

ERROR: 
No query specified

mysql>

對比測試環境和線上環境,發現 definer有差別,一個是root@%,一個是root@localhost。 DBA在線上執行:GRANT SELECT ON mysql.proc TO 'test'@'localhost'; 後,問題獲得解決

總結:

  • 若是definer是root@localhost,須要執行 GRANT SELECT ON mysql.proc TO 'test'@'%';
  • 若是definer是root@%,須要執行 ALTER PROCEDURE new_dove_table SQL SECURITY INVOKER;

緣由: 存儲過程的執行流程: 一、查找存儲過程 存儲過程就在mysql.proc中,查詢時須要根據Security_type的類型進行權限檢查。 若是Security_type值爲INVOKER,則已經具備查詢mysql.proc的權限,所以不須要GRANT SELECT ON mysql.proc TO 'user'@'%';賦權限若是若是Security_type值爲DEFINER,則須要看調用者是否具備存儲過程的權限,默認沒有查詢權限,所以須要GRANT SELECT ON mysql.proc TO 'user'@'%';賦權限

二、解析存儲過程的參數 解析存儲過程傳遞參數和存儲過程定義是否符合。

三、執行存儲過程 檢查執行者的權限,執行者由Security_type決定。若是是INVOKER則,檢查存儲過程調用者的權限和存儲過程內要求的權限是否匹配; 若是是DEFINER,檢查存儲過程的DEFINER權限和存儲過程內要求的權限是否匹配。

擴展信息

存儲過程的執行權限

mysql> grant execute on procedure test.new_dove_table to 'test'@'%';

mysql> revoke execute procedure test.new_dove_table from test;

JDBC鏈接執行MySQL存儲過程報權限錯誤

相關文章
相關標籤/搜索