MySQL實戰45講學習筆記:第四十二講

1、本節概述

在 MySQL 裏面,grant 語句是用來給用戶賦權的。不知道你有沒有見過一些操做文檔裏面提到,grant 以後要立刻跟着執行一個 flush privileges 命令,才能使賦權語句生效。
我最開始使用 MySQL 的時候,就是照着一個操做文檔的說明按照這個順序操做的。mysql

那麼,grant 以後真的須要執行 flush privileges 嗎?若是沒有執行這個 flush 命令的話,賦權語句真的不能生效嗎?sql

接下來,我就先和你介紹一下 grant 語句和 flush privileges 語句分別作了什麼事情,而後再一塊兒來分析這個問題。數據庫

爲了便於說明,我先建立一個用戶:數組

create user 'ua'@'%' identified by 'pa';

這條語句的邏輯是建立一個用戶’ua’@’%’,密碼是 pa。注意,在 MySQL 裏面,用戶名 (user)+ 地址 (host) 才表示一個用戶,所以 ua@ip1 和 ua@ip2 表明的是兩個不一樣的用戶。bash

這條命令作了兩個動做:session

1. 磁盤上,往 mysql.user 表裏插入一行,因爲沒有指定權限,因此這行數據上全部表示權限的字段的值都是 N;
2. 內存裏,往數組 acl_users 裏插入一個 acl_user 對象,這個對象的 access 字段值爲0。ide

圖 1 就是這個時刻用戶 ua 在 user 表中的狀態。工具

圖 1 mysql.user 數據行spa

在 MySQL 中,用戶權限是有不一樣的範圍的。接下來,我就按照用戶權限範圍從大到小的順序依次和你說明。操作系統

2、全局權限

全局權限,做用於整個 MySQL 實例,這些權限信息保存在 mysql 庫的 user 表裏。若是我要給用戶 ua 賦一個最高權限的話,語句是這麼寫的:

grant all privileges on *.* to 'ua'@'%' with grant option;

這個 grant 命令作了兩個動做:

1. 磁盤上,將 mysql.user 表裏,用戶’ua’@’%'這一行的全部表示權限的字段的值都修改成‘Y’;
2. 內存裏,從數組 acl_users 中找到這個用戶對應的對象,將 access 值(權限位)修改成二進制的「全 1」。

在這個 grant 命令執行完成後,若是有新的客戶端使用用戶名 ua 登陸成功,MySQL 會爲新鏈接維護一個線程對象,而後從 acl_users 數組裏查到這個用戶的權限,並將權限值拷
貝到這個線程對象中。以後在這個鏈接中執行的語句,全部關於全局權限的判斷,都直接使用線程對象內部保存的權限位。

基於上面的分析咱們能夠知道:

1. grant 命令對於全局權限,同時更新了磁盤和內存。命令完成後即時生效,接下來新建立的鏈接會使用新的權限。
2. 對於一個已經存在的鏈接,它的全局權限不受 grant 命令的影響。

須要說明的是,通常在生產環境上要合理控制用戶權限的範圍。咱們上面用到的這個grant 語句就是一個典型的錯誤示範。若是一個用戶有全部權限,通常就不該該設置爲全部 IP 地址均可以訪問。

若是要回收上面的 grant 語句賦予的權限,你可使用下面這條命令:

revoke all privileges on *.* from 'ua'@'%';

這條 revoke 命令的用法與 grant 相似,作了以下兩個動做:

1. 磁盤上,將 mysql.user 表裏,用戶’ua’@’%'這一行的全部表示權限的字段的值都修改成「N」;

2. 內存裏,從數組 acl_users 中找到這個用戶對應的對象,將 access 的值修改成 0。

3、db 權限

一、grant 命令工做流程

除了全局權限,MySQL 也支持庫級別的權限定義。若是要讓用戶 ua 擁有庫 db1 的全部權限,能夠執行下面這條命令:

grant all privileges on db1.* to 'ua'@'%' with grant option;

基於庫的權限記錄保存在 mysql.db 表中,在內存裏則保存在數組 acl_dbs 中。這條grant 命令作了以下兩個動做:

1. 磁盤上,往 mysql.db 表中插入了一行記錄,全部權限位字段設置爲「Y」;
2. 內存裏,增長一個對象到數組 acl_dbs 中,這個對象的權限位爲「全 1」。

圖 2 就是這個時刻用戶 ua 在 db 表中的狀態。

圖 2 mysql.db 數據行

每次須要判斷一個用戶對一個數據庫讀寫權限的時候,都須要遍歷一次 acl_dbs 數組,根據 user、host 和 db 找到匹配的對象,而後根據對象的權限位來判斷。
也就是說,grant 修改 db 權限的時候,是同時對磁盤和內存生效的

二、grant 操做對於已經存在的鏈接的影響,在全局權限和基於 db 的權限效果是不一樣的。

grant 操做對於已經存在的鏈接的影響,在全局權限和基於 db 的權限效果是不一樣的。接下來,咱們作一個對照試驗來分別看一下。

圖 3 權限操做效果

須要說明的是,圖中 set global sync_binlog 這個操做是須要 super 權限的。

能夠看到,雖然用戶 ua 的 super 權限在 T3 時刻已經經過 revoke 語句回收了,可是在T4 時刻執行 set global 的時候,權限驗證仍是經過了。這是由於 super 是全局權限,這
個權限信息在線程對象中,而 revoke 操做影響不到這個線程對象。

而在 T5 時刻去掉 ua 對 db1 庫的全部權限後,在 T6 時刻 session B 再操做 db1 庫的表,就會報錯「權限不足」。這是由於 acl_dbs 是一個全局數組,全部線程判斷 db 權限
都用這個數組,這樣 revoke 操做立刻就會影響到 session B。

這裏在代碼實現上有一個特別的邏輯,若是當前會話已經處於某一個 db 裏面,以前 use這個庫的時候拿到的庫權限會保存在會話變量中。

你能夠看到在 T6 時刻,session C 和 session B 對錶 t 的操做邏輯是同樣的。可是session B 報錯,而 session C 能夠執行成功。這是由於 session C 在 T2 時刻執行的 use
db1,拿到了這個庫的權限,在切換出 db1 庫以前,session C 對這個庫就一直有權限。

4、表權限和列權限

除了 db 級別的權限外,MySQL 支持更細粒度的表權限和列權限。其中,表權限定義存放在表 mysql.tables_priv 中,列權限定義存放在表 mysql.columns_priv 中。這兩類權
限,組合起來存放在內存的 hash 結構 column_priv_hash 中。

這兩類權限的賦權命令以下:

create table db1.t1(id int, a int);

grant all privileges on db1.t1 to 'ua'@'%' with grant option;
GRANT SELECT(id), INSERT (id,a) ON mydb.mytbl TO 'ua'@'%' with grant option;

跟 db 權限相似,這兩個權限每次 grant 的時候都會修改數據表,也會同步修改內存中的hash 結構。所以,對這兩類權限的操做,也會立刻影響到已經存在的鏈接。

看到這裏,你必定會問,看來 grant 語句都是即時生效的,那這麼看應該就不須要執行flush privileges 語句了呀。

答案也確實是這樣的。

flush privileges 命令會清空 acl_users 數組,而後從 mysql.user 表中讀取數據從新加載,從新構造一個 acl_users 數組。也就是說,以數據表中的數據爲準,會將全局權限內存數組從新加載一遍。

一樣地,對於 db 權限、表權限和列權限,MySQL 也作了這樣的處理。

也就是說,若是內存的權限數據和磁盤數據表相同的話,不須要執行 flush privileges。而若是咱們都是用 grant/revoke 語句來執行的話,內存和數據表原本就是保持同步更新的。

所以,正常狀況下,grant 命令以後,沒有必要跟着執行 flush privileges 命令。

5、flush privileges 使用場景

那麼,flush privileges 是在何時使用呢?顯然,當數據表中的權限數據跟內存中的權限數據不一致的時候,flush privileges 語句能夠用來重建內存數據,達到一致狀態。

這種不一致每每是由不規範的操做致使的,好比直接用 DML 語句操做系統權限表。咱們來看一下下面這個場景:

圖 4 使用 flush privileges

能夠看到,T3 時刻雖然已經用 delete 語句刪除了用戶 ua,可是在 T4 時刻,仍然能夠用ua 鏈接成功。緣由就是,這時候內存中 acl_users 數組中還有這個用戶,所以系統判斷時
認爲用戶還正常存在。

在 T5 時刻執行過 flush 命令後,內存更新,T6 時刻再要用 ua 來登陸的話,就會報錯「沒法訪問」了。

直接操做系統表是不規範的操做,這個不一致狀態也會致使一些更「詭異」的現象發生。好比,前面這個經過 delete 語句刪除用戶的例子,就會出現下面的狀況:

圖 5 不規範權限操做致使的異常

能夠看到,因爲在 T3 時刻直接刪除了數據表的記錄,而內存的數據還存在。這就致使了:

1. T4 時刻給用戶 ua 賦權限失敗,由於 mysql.user 表中找不到這行記錄;
2. 而 T5 時刻要從新建立這個用戶也不行,由於在作內存判斷的時候,會認爲這個用戶還存在。

6、小結

今天這篇文章,我和你介紹了 MySQL 用戶權限在數據表和內存中的存在形式,以及grant 和 revoke 命令的執行邏輯。

grant 語句會同時修改數據表和內存,判斷權限的時候使用的是內存數據。所以,規範地使用 grant 和 revoke 語句,是不須要隨後加上 flush privileges 語句的。

flush privileges 語句自己會用數據表的數據重建一分內存權限數據,因此在權限數據可能存在不一致的狀況下再使用。而這種不一致每每是因爲直接用 DML 語句操做系統權限表
致使的,因此咱們儘可能不要使用這類語句。

另外,在使用 grant 語句賦權時,你可能還會看到這樣的寫法:

grant super on *.* to 'ua'@'%' identified by 'pa';

這條命令加了 identified by ‘密碼’, 語句的邏輯裏面除了賦權外,還包含了:

1. 若是用戶’ua’@’%'不存在,就建立這個用戶,密碼是 pa;
2. 若是用戶 ua 已經存在,就將密碼修改爲 pa。這也是一種不建議的寫法,由於這種寫法很容易就會不慎把密碼給改了。

「grant 以後隨手加 flush privileges」,我本身是這麼使用了兩三年以後,在看代碼的時候才發現其實並不須要這樣作,那已是 2011 年的事情了。

去年我看到一位小夥伴這麼操做的時候,指出這個問題時,他也以爲很神奇。由於,他和我同樣看的第一份文檔就是這麼寫的,本身也一直是這麼用的。

因此,今天的課後問題是,請你也來講一說,在使用數據庫或者寫代碼的過程當中,有沒有遇到過相似的場景:誤用了很長時間之後,因爲一個契機發現「啊,原來我錯了這麼久」?

你能夠把你的經歷寫在留言區,我會在下一篇文章的末尾選取有趣的評論和你分享。感謝你的收聽,也歡迎你把這篇文章分享給更多的朋友一塊兒閱讀。

7、上期問題時間

上期的問題是,MySQL 解析 statement 格式的 binlog 的時候,對於 load data 命令,解析出來爲何用的是 load data local。

這樣作的一個緣由是,爲了確保備庫應用 binlog 正常。由於備庫可能配置了

secure_file_priv=null,因此若是不用 local 的話,可能會導入失敗,形成主備同步延遲。

另外一種應用場景是使用 mysqlbinlog 工具解析 binlog 文件,並應用到目標庫的狀況。你可使用下面這條命令 :

mysqlbinlog $binlog_file | mysql -h$host -P$port -u$user -p$pwd

把日誌直接解析出來發給目標庫執行。增長 local,就能讓這個方法支持非本地的 $host。

評論區留言點贊板:

@poppy 、@庫淘淘 兩位同窗提到了第一個場景;@王顯偉 @lionetes 兩位同窗幫忙回答了 @undifined 同窗的疑問,拷貝出來的文件要確保 MySQL 進程能夠讀。

相關文章
相關標籤/搜索