前言:
我們在創建數據庫用戶的時候都會指定host,即一個完整的用戶可描述爲 'username'@'host' 。創建用戶時不顯式指定host則默認爲%,%代表所有ip段都可以使用這個用戶,我們也可以指定host爲某個ip或ip段,這樣會僅允許在指定的ip主機使用該數據庫用戶。不過你也應該明白 'username'@'%' 和 'username'@'192.168.6.%' 是兩個毫無關聯的用戶,這兩個用戶可以有不同的密碼和權限,這裏不建議創建多個同名不同host的用戶,還有不要輕易更改用戶的host,筆者曾經遇到過因爲更改用戶host引發的故障,下面將其分享出來,爲你講述前因後果。
1.故障模擬
當時爲了規範安全,將某個程序用戶的host由%改爲了應用服務器ip段,過段時間業務反饋某些功能報錯,經排查發現是因爲無法調用存儲過程(大家可以先思考下原因),下面模擬下故障操作。
# 原有用戶、表、存儲過程模擬創建
mysql> create user 'testuser'@'%' identified by '123456';
Query OK, 0 rows affected (0.04 sec)
mysql> grant select,insert,update,delete,execute on `testdb`.* to 'testuser'@'%';
Query OK, 0 rows affected (0.01 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> show grants for 'testuser'@'%';
+-------------------------------------------------------------------------------+
| Grants for testuser@% |
+-------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'testuser'@'%' |
| GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON `testdb`.* TO 'testuser'@'%' |
+-------------------------------------------------------------------------------+
CREATE TABLE `students` (
`id` int(11) NOT NULL ,
`name` varchar(20),
`age` int(11),
PRIMARY KEY (`id`)
) ENGINE=InnoDB ;
INSERT INTO `students` VALUES ('1001', 'lodd', '23');
INSERT INTO `students` VALUES ('1002', 'sdfs', '21');
INSERT INTO `students` VALUES ('1003', 'sdfsa', '24');
DROP PROCEDURE IF EXISTS select_students_count;
DELIMITER $$
CREATE DEFINER=`testuser`@`%` PROCEDURE `select_students_count`()
BEGIN
SELECT count(id) from students;
END
$$
DELIMITER ;
# 使用testuser用戶調用存儲過程 調用正常
mysql> call select_students_count();
+-----------+
| count(id) |
+-----------+
| 3 |
+-----------+
# 更改用戶host 重命名用戶
mysql> RENAME USER 'testuser'@'%' to 'testuser'@'192.168.6.%';
Query OK, 0 rows affected (0.00 sec)
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
mysql> show grants for 'testuser'@'192.168.6.%';
+---------------------------------------------------------------------------------------+
| Grants for testuser@localhost |
+---------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'testuser'@'localhost' |
| GRANT SELECT, INSERT, UPDATE, DELETE, EXECUTE ON `testdb`.* TO 'testuser'@'localhost' |
+---------------------------------------------------------------------------------------+
# 再次用testuser用戶調用存儲過程 無法調用 出現故障
mysql> call select_students_count();
ERROR 1449 (HY000): The user specified as a definer ('testuser'@'%') does not exist
2.故障排查與解決
其實我們手動調用下存儲過程後,從報錯內容明顯可以看出是因爲'testuser@'%'用戶不存在的問題。因爲該存儲過程的定義者是'testuser@'%',而我們將此用戶的host改成了192.168.6.%,那麼當我們之後調用該存儲過程時,系統判別到此存儲過程的屬主用戶不存在,因此係統拒絕請求並拋出異常。
當知道上述原因後,解決方法就會明朗許多,我們只需要將該存儲過程的屬主改爲新的用戶即可。其實更改過用戶後,該用戶下的視圖、存儲過程、函數、觸發器、事件都會受到影響,當我們定義視圖、存儲過程、函數時使用 DEFINER
屬性時,若調用這些對象,系統會首先判別此對象的屬主用戶是否存在,不存在會直接拋出錯誤。
此問題的解決方案有兩種,一是將此存儲過程的安全屬性由 DEFINER
改爲 INVOKER
,個人不推薦這個方案,至於 DEFINER
和 INVOKER
的區別,下個章節會額外講解。二是更改此存儲過程的屬主,下面給出更改方法並加以驗證:
# 通過系統表更改存儲過程的屬主
mysql> update mysql.proc set definer='[email protected].%' where db='testdb' and name='select_students_count' and type='PROCEDURE';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
# 使用testuser用戶調用驗證 調用成功
mysql> call select_students_count();
+-----------+
| count(id) |
+-----------+
| 3 |
+-----------+
1 row in set (0.00 sec)
3.DEFINER與INVOKER拓展知識
MySQL中,創建視圖(view)、函數(function)、存儲過程(procedure)、觸發器(trigger)、事件(event)時,可以指定安全驗證方式(也就是SQL SECURITY)屬性,其值可以爲DEFINER或INVOKER,表示在執行過程中,使用誰的權限來執行。
- DEFINER:由definer(定義者)指定的用戶的權限來執行
- INVOKER:由調用這個視圖(存儲過程)的用戶的權限來執行
默認情況下,系統指定爲DEFINER。當SQL SECURITY屬性爲DEFINER時,數據庫中必須存在DEFINER指定的用戶,並且該用戶擁有對應的操作權限及引用的相關對象的權限,才能成功執行。與當前用戶是否有權限無關。當SQL SECURITY屬性爲INVOKER時,只要執行者有執行權限並且有引用的相關對象的權限,就可以成功執行。
瞭解了上述知識後,可能你早已明白上述故障發生的前因後果。在日常生產中,不建議使用INVOKER屬性,因爲將SQL SECURITY定義爲INVOKER後,其他用戶想調用此對象時不僅需要有該對象的執行權限還要有其他引用到的相關對象的權限,極大的增加了運維複雜性。下面回顧整篇文章,整理出一下幾點個人建議,以供大家參考:
- 不創建多個同名不同host的用戶。
- 不要輕易更改用戶的host。
- 更改用戶host請用RENAME USER語句,直接更新mysql.user系統表中的host屬性會使權限丟失。
- 更改用戶host後,要注意此用戶下的各個對象的DEFINER屬性。
- 創建視圖、存儲過程等對象建議將SQL SECURITY定義爲DEFINER。
- 數據庫遷移時,要注意新環境存在相關對象定義的DEFINER用戶。
總結:
本文從一個故障出發,詳細記錄了故障發生的原因及背後涉及的知識,其實像DEFINER屬性這些細節類的東西很容易被忽視,只有遇到問題了我們纔會去探究。希望本篇文章能讓你學到新東西,特別是上面總結的幾點建議都是筆者日常運維總結出的。原創不易,請大家多多支持!