30.5. MySQL用戶,權限,服務器配置,查詢緩存及優化,索引等

MySQL用戶和權限管理

  • 元數據數據庫:mysql
    系統授權表:
    db, host, user
    columns_priv, tables_priv, procs_priv, proxies_priv
  • 用戶賬號:

'USERNAME'@'HOST'

@'HOST':

  1. 主機名
  2. IP地址或Network
    可用通配符: % _
    示例:172.16.%.%

用戶管理

  1. 創建用戶:CREATE USER
    CREATE USER 'USERNAME'@'HOST' [IDENTIFIED BY 'password'];
    默認權限:USAGE(用戶權限)
  2. 用戶重命名:RENAME USER
    RENAME USER old_user_name TO new_user_name;
  3. 刪除用戶:
    DROP USER 'USERNAME'@'HOST'
    示例:刪除默認的空用戶
    DROP USER ''@'localhost';
  4. 修改密碼:

    1. mysql>SET PASSWORD FOR 'user'@'host' = PASSWORD('password');
    2. mysql>UPDATE mysql.user SET password=PASSWORD('password') WHERE clause(比如說user='root');
      注意第二種改表的方式需要執行下面指令才能生效:
      mysql> FLUSH PRIVILEGES; (刷新權限)
      當然重新啓動服務也可。
    3. #mysqladmin -u root -poldpass password ‘newpass’
  5. 忘記管理員密碼的解決辦法:
    1. 重新啓動mysqld進程時,爲其使用如下選項(用在safe腳本上)(如果寫入配置文件中my.cnf注意把前面的--去掉):
      --skip-grant-tables :不檢查授權表,所有用戶都變成了root權限
      --skip-networking :不讓網絡用戶連接
    2. 使用UPDATE命令修改管理員密碼(set password命令此模式下用不了)
    3. 關閉mysqld進程,移除上述兩個選項,重啓mysqld

權限管理

權限類別:
管理類
程序類
數據庫級別
表級別
字段級別

  1. 管理類:
    CREATE TEMPORARY TABLES
    CREATE USER
    FILE
    SUPER
    SHOW DATABASES
    RELOAD
    SHUTDOWN
    REPLICATION SLAVE
    REPLICATION CLIENT
    LOCK TABLES
    PROCESS
  2. 程序類: FUNCTION、 PROCEDURE、 TRIGGER
    CREATE
    ALTER
    DROP
    EXCUTE
  3. 庫和表級別:DATABASE、 TABLE
    ALTER
    CREATE
    CREATE VIEW
    DROP
    INDEX
    SHOW VIEW
    GRANT OPTION:能將自己獲得的權限轉贈給其他用戶
  4. 數據操作
    SELECT
    INSERT
    DELETE
    UPDATE
  5. 字段級別
    SELECT(col1,col2,...)
    UPDATE(col1,col2,...)
    INSERT(col1,col2,...)
  6. 所有權限
    ALL PRIVILEGES 或 ALL

授權

參考:https://dev.mysql.com/doc/refman/5.7/en/grant.html
https://mariadb.com/kb/en/library/grant/
GRANT priv_type [(column_list)],... ON [object_type] priv_level TO 'user'@'host' [IDENTIFIED BY 'password'] [WITH GRANT OPTION];
其中:
priv_type: ALL [PRIVILEGES]
object_type:TABLE | FUNCTION | PROCEDURE
priv_level: (所有庫) | . | db_name. | db_name.tbl_name | tbl_name(當前庫的表) | db_name.routine_name(指定庫的函數,存儲過程,觸發器)
with_option: GRANT OPTION
| MAX_QUERIES_PER_HOUR count
| MAX_UPDATES_PER_HOUR count
| MAX_CONNECTIONS_PER_HOUR count
| MAX_USER_CONNECTIONS count

示例:GRANT SELECT (col1), INSERT (col1,col2) ON mydb.mytbl TO 'someuser'@'somehost';

回收授權

REVOKE priv_type [(column_list)] [, priv_type [(column_list)]] ... ON [object_type] priv_level FROM user [, user] ...
示例:REVOKE DELETE ON testdb.* FROM 'testuser'@‘172.16.0.%’;

  • 查看指定用戶獲得的授權
    Help SHOW GRANTS
    SHOW GRANTS FOR 'user'@'host';
    SHOW GRANTS FOR CURRENT_USER[()];
  • 注意:MariaDB服務進程啓動時會讀取mysql庫中所有授權表至內存
    (1) GRANT或REVOKE等執行權限操作會保存於系統表中,MariaDB的服務進程通常會自動重讀授權表,使之生效
    (2) 對於不能夠或不能及時重讀授權表的命令,可手動讓MariaDB的服務進程重讀授權表:mysql> FLUSH PRIVILEGES;

注意點1(用戶權限和授權):

  1. 本機上如果創建了可以遠程連接的用戶比如說 'zhang'@'192.168.36.%' , 此時如果user表中仍然有匿名用戶的localhost主機的話,則在本機上照樣可以用mysql -uzhang -h127.1的方式來連接。
    • 注意這裏的匿名用戶的zhang和創建的可以遠程連接的用戶zhang並非是同一個賬戶。
    • 因此爲了安全一定要刪除掉匿名賬戶的連接方式。利用drop user 刪除掉這些賬戶。
  2. 注意,在當前的數據庫創建的用戶後面指定的host是允許遠程的主機連接的主機IP(或者主機名),而並非是本機上的。不要混淆了。連接的時候注意如果端口號不同被忘了加上-P選項指定端口號。
    • 默認用create命令創建的用戶的權限很小(usage)。
    • 注意了,如果用create 創建一個root賬號,雖然它的名字和超級管理員的root名字是一致的,但是它權限仍然是usage的很小的權限。
  3. select password('string') 此命令可以查看srting(一定要用單引號引起來)被轉化爲hash加密後的密碼結果,可用這種方式修改用戶的加密密碼。
    • 可以用help password查看它的詳細說明,其中有個變量old_passwords的值能夠影響次函數的hash結果的長度
    • 可用show create table mysql.user\G ;和自己設置明文密碼測試得知密碼必須爲41個十六進制數,不能設置明文密碼。
    • 注意當創建用戶的時候如果同時設置密碼(identified),不需要password函數,只有修改密碼的時候(兩種方式修改都)才需要password函數對密碼進行加密。
  4. 注意忘記密碼利用skip-grant-tables修改密碼的的時候,是不能使用DDL語言的(比如drop user name@host),但可以用DML語言進行表中的增刪改,因此此時用update命令用修改表的方式修改密碼即可。
    • --skip-networking選項一定要加上。
  5. 權限設置的時候可以精確到字段的級別(更不用說表和數據庫級別了),因此還是非常靈活的。
    • 授權grant的命令可以同時創建用戶,不過要注意必須加上identified by 'password' 選項 (創建的時候必須加上,授權的時候不加,不然報錯,不過後面可知這是因爲SQL_mode的默認模式造成不能創建空用戶的原因)因此大多就是用這個命令來進行創建用戶的。
    • with grant option 參數可以讓當前被授權的用戶擁有授權給其他用戶的權限的能力,不過能夠授權給其他用戶的權限必須是這個用戶所擁有的權限。
    • 幫助中有詳解:To use GRANT, you must have the GRANT OPTION privilege, and you must have theprivileges that you are granting.
    • 示例比如說grant all on wordpress.* to wpsuser@'192.168.36.%' identified by '123456' ; 注意此命令雖然授權了全部的權限,但是它是在wordpress這個數據庫上的全部權限,它仍然不能看到其它的數據庫和對它們進行操作。
  6. 注意權限設置之後需要重新開啓session(終端)重新登錄或者先退出連接再重新連接才能生效,雖然用show grants命令查看到的權限是正確的,但不代表當前直接就能用這些新授權的權限了
    • 也可多利用flush privileges命令,它不僅可以設置密碼生效,也可設置權限生效,不過經過測試對於權限來說它沒用~~,還是需要重新登錄連接
    • 重要注意點1:經過測試,以系統創建的root身份登錄之後,它可以把這些系統創建的超級管理員的權限(也就是自己的權限)給revoke掉的(因爲登陸的時候這個root權限是ALL權限,已經載入內存了,所以此時就算刪除掉自己的權限,也不會在當前session中受影響。不過如果退出後再次連接的時候這些被刪除的權限就真的被刪除了
    • 重要注意點2:而且,如果root的某些權限被刪除掉之後,user表中再也沒有其他的用戶擁有這個被刪除的權限以及同時擁有with option權限的話,這個被刪除的權限則永遠無法被再次授權出來了(因爲用戶必須要有這個權限,同時有with option才能給其他用戶授權這個權限),此時就崩了~~
    • 重要注意點3:經過測試就算是利用skip-grant-tables模式,也無法再把root被刪除的權限給找回來了,因爲在這個模式下,無法使用grant命令,甚至無法查看show grants;
  7. 還有一個代理權限(匿名用戶用,和PAM模塊有關),這個以後再討論。基本上都是刪除掉匿名用戶。

MySQL體系結構

image

存儲引擎

InnoDB support for FULLTEXT indexes is available in MySQL 5.6.4 and later.
存儲引擎比較: https://docs.oracle.com/cd/E17952_01/mysql-8.0-en/storage-engines.html

目前主要用InnoDB存儲引擎,老版本的數據庫用的MyISAM存儲引擎,這裏需要了解它倆的區別。

MyISAM引擎:

MyISAM引擎特點

不支持事務
表級鎖定
讀寫相互阻塞,寫入不能讀,讀時不能寫
只能緩存索引,不能緩存數據
不支持外鍵約束
不支持聚簇索引
支持全文索引
讀取數據較快,佔用資源較少
不支持MVCC(多版本併發控制機制)高併發
崩潰恢復性較差(因爲不支持事務)
MySQL5.5.5前默認的數據庫引擎

MyISAM存儲引擎適用場景

只讀(或者寫較少)、表較小(可以接受長時間進行修復操作)

MyISAM引擎文件

tbl_name.frm 表格式定義
tbl_name.MYD 數據文件
tbl_name.MYI 索引文件

InnoDB引擎:

InnoDB引擎特點

行級鎖
支持事務,適合處理大量短期事務
讀寫阻塞與事務隔離級別相關
可緩存數據和索引
支持外鍵約束
支持聚簇索引
崩潰恢復性更好
支持MVCC高併發
從MySQL5.5後支持全文索引
從MySQL5.5.5開始爲默認的數據庫引擎

InnoDB數據庫文件

  1. 一種方式是(低版本默認):所有InnoDB表的數據和索引放置於同一個表空間中
    表空間文件:datadir定義的目錄下
    數據文件:ibddata1, ibddata2, ...
  2. 另外一種方式(推薦,高版本默認開啓):每個表單獨使用一個表空間存儲表的數據和索引
    啓用:innodb_file_per_table=ON (寫在my.cnf配置文件中)
    參看:https://mariadb.com/kb/en/library/xtradbinnodb-server
    system-variables/#innodb_file_per_table
    ON (>= MariaDB 5.5)
    兩類文件放在數據庫的database獨立目錄中
    數據文件(存儲數據和索引):tb_name.ibd
    表格式定義:tb_name.frm

其它存儲引擎

  1. Performance_Schema:Performance_Schema數據庫使用
  2. Memory :將所有數據存儲在RAM中,以便在需要快速查找參考和其他類似數據的環境中進行快速訪問。適用存放臨時數據。引擎以前被稱爲HEAP引擎
  3. MRG_MyISAM:使MySQL DBA或開發人員能夠對一系列相同的MyISAM表進行邏輯分組,並將它們作爲一個對象引用。適用於VLDB(Very Large Data Base)環境,如數據倉庫
  4. Archive :爲存儲和檢索大量很少參考的存檔或安全審覈信息,只支持SELECT和INSERT操作;支持行級鎖和專用緩存區
  5. Federated聯合:用於訪問其它遠程MySQL服務器一個代理,它通過創建一個到遠程MySQL服務器的客戶端連接,並將查詢傳輸到遠程服務器執行,而後完成數據存取,提供鏈接單獨MySQL服務器的能力,以便從多個物理服務器創建一個邏輯數據庫。非常適合分佈式或數據集市環境
  6. BDB:可替代InnoDB的事務引擎,支持COMMIT、 ROLLBACK和其他事務特性
  7. Cluster/NDB:MySQL的簇式數據庫引擎,尤其適合於具有高性能查找要求的應用程序,這類查找需求還要求具有最高的正常工作時間和可用性
  8. CSV:CSV存儲引擎使用逗號分隔值格式將數據存儲在文本文件中。可以使用CSV引擎以CSV格式導入和導出其他軟件和應用程序之間的數據交換
  9. BLACKHOLE :黑洞存儲引擎接受但不存儲數據,檢索總是返回一個空集。該功能可用於分佈式數據庫設計,數據自動複製,但不是本地存儲
  10. example:“stub”引擎,它什麼都不做。可以使用此引擎創建表,但不能將數據存儲在其中或從中檢索。目的是作爲例子來說明如何開始編寫新的存儲引擎

MariaDB支持的其它存儲引擎:
OQGraph
SphinxSE
TokuDB
Cassandra
CONNECT
SQUENCE

管理存儲引擎

  1. 查看mysql支持的存儲引擎
    show engines;
  2. 查看當前默認的存儲引擎
    show variables like '%storage_engine%';
  3. 設置默認的存儲引擎
    vim /etc/my.conf
    [mysqld]
    default_storage_engine= InnoDB
  4. 查看庫中所有表使用的存儲引擎
    show table status from db_name;
  5. 查看庫中指定表的存儲引擎
    show table status like ' tb_name ';
    show create table tb_name;
  6. 設置表的存儲引擎:
    CREATE TABLE tb_name(... ) ENGINE=InnoDB;
    ALTER TABLE tb_name ENGINE=InnoDB;
    MySQL中的系統數據庫

mysql系統數據庫

  1. mysql數據庫
    是mysql的核心數據庫,類似於SqlServer中的master庫,主要負責存儲數據庫的用戶、權限設置、關鍵字等mysql自己需要使用的控制和管理信息
  2. performance_schema數據庫
    MySQL 5.5開始新增的數據庫,主要用於收集數據庫服務器性能參數,庫裏表的存儲引擎均爲PERFORMANCE_SCHEMA,用戶不能創建存儲引擎爲 PERFORMANCE_SCHEMA的表
  3. information_schema數據庫
    MySQL 5.0之後產生的,一個虛擬數據庫,物理上並不存在 information_schema數據庫類似與“數據字典”,提供了訪問數據庫元數據的方式,即數據的數據。比如數據庫名或表名,列類型,訪問權限(更加細化的訪問方式)

注意點2(存儲引擎):

  1. Myisam和innodb雖然都支持壓縮數據,不過前者只能對row進行壓縮(且壓縮後變成了只讀的),因此後者所佔空間相對而言會更少。
  2. MYiasm是表級鎖,一個用戶修改這個表的數據時,另外一個用戶必須等待(另外的用戶可以修改別的表),innodb則是行級鎖,度用戶可以同時修改一張表,只要是不同的行row即可。因此innodb併發性比較好。
  3. 重要注意點:innodb支持MVCC高併發
    • 關於MVCC高併發的簡單解釋就是,在innodb引擎的數據庫裏,每一張表不僅僅有定義的可見字段,其中系統還會給它後面加上兩個隱藏的字段insert和delete,這兩個字段中記錄的是對應的行的操作的事件ID
    • 事件ID就是相當於當前操作的一個編號,每操作一次就加1。隱藏的兩個字段insert就代表此行添加的時候的事件ID,delete代表刪除時的事件ID。如果對一個行進行了update修改操作,就相當於是先delete後insert,此時的這兩項是相同的(一般都是delete大於insert),這兩項均爲當前進行update操作的事件ID
    • 因此,通過這個事件ID,當使用DQL語句(select命令)的時候,因爲不管是什麼操作都有事件ID,就可以根據當前select的操作的事件ID來顯示不同的結果;
    • 比如說當前select操作的事件ID爲300,則在表中300事件ID之前的所有結果(行)都會被顯示出來,而300之後(相當於還未做這些操作)當然也就不會被顯示出來了。
    • 注意在上面的例子中,300事件ID之前被被刪除的行(delete值大於insert值)是不會顯示出來的,因爲已經被刪除了。只有那些insert存在而delete爲空的和insert和delete相等的事件ID的行纔會被顯示出來。
    • 由此也可以看出,數據庫的delete命令並未直接就把表中的記錄record行給刪除掉了,而是說把它加上了當前刪除命令delete的事件ID,當再用其他的命令比如select的時候,根據它的事件ID來選擇顯示結果等,給人一種確實把record刪除掉的效果。這也是一種數據庫的設計思路,能保證短期內的數據安全。
  4. 創建新的表的時候(注意是新的表table,而不是database,database就是個文件夾而已,沒有存儲引擎的說法)會按照當前mysql服務器設置的默認存儲引擎來創建這個數據庫,新版本已經是innodb了,如果是老版本的話按照上面介紹的命令操作進行設置修改爲innodb即可。

服務器配置

mysqld選項,服務器系統變量和服務器狀態變量(注意3個的區分)

https://dev.mysql.com/doc/refman/8.0/en/server-option-variable-reference.html
https://mariadb.com/kb/en/library/full-list-of-mariadb-options-system-and-status-variables/

  • 注意:
    其中有些參數支持運行時修改,會立即生效(Dynamic);
    有些參數不支持,且只能通過修改配置文件,並重啓服務器程序生效;
    有些參數作用域是全局的,且不可改變;有些可以爲每個用戶提供單獨(會話)的設置

獲取mysqld的可用選項列表:

mysqld --help --verbose
mysqld --print-defaults 獲取默認設置

設置服務器選項方法:

  1. 在命令行中設置
    shell> ./mysqld_safe --skip-name-resolve=1
    • 注意這樣就啓動了這個mysql服務了,一般這種都是寫到腳本中的,或者就是按照下面2中的寫到配置文件中即可。
  2. 在配置文件my.cnf中設置
    skip_name_resolve=1

服務器系統變量:分全局和會話兩種

  1. 獲取系統變量
    mysql> SHOW GLOBAL VARIABLES;
    mysql> SHOW [SESSION] VARIABLES;
    mysql> SHOW VARIABLES LKIE '%VARIABLE%';
    mysql> SELECT @@VARIABLES;
  2. 修改服務器變量的值:
    mysql> help SET
  3. 修改全局變量:僅對修改後新創建的會話有效;對已經建立的會話無效
    mysql> SET GLOBAL system_var_name=value;
    mysql> SET @@global.system_var_name=value;
  4. 修改會話變量:
    mysql> SET [SESSION] system_var_name=value;
    mysql> SET @@[session.]system_var_name=value;

服務器狀態變量:

分全局和會話兩種
狀態變量(只讀):用於保存mysqld運行中的統計數據的變量,不可更改
mysql> SHOW GLOBAL STATUS;
mysql> SHOW [SESSION] STATUS;

服務器變量SQL_MODE

SQL_MODE:對其設置可以完成一些約束檢查的工作,可分別進行全局的設置或當前會話的設置,參看:https://mariadb.com/kb/en/library/sql-mode/
此變量可同時擁有許多值,常見MODE:

  1. NO_AUTO_CREATE_USER
    禁止GRANT創建密碼爲空的用戶
  2. NO_ZERO_DATE
    在嚴格模式,不允許使用‘0000-00-00’ 的時間
  3. ONLY_FULL_GROUP_BY
    對於GROUP BY聚合操作,如果在SELECT中的列,沒有在GROUP BY中出現,那麼將認爲這個SQL是不合法的
  4. NO_BACKSLASH_ESCAPES
    反斜槓“\” 作爲普通字符而非轉義字符
  5. PIPES_AS_CONCAT
    將"||"視爲連接操作符而非“或運算符”

注意點3(服務器配置):

  1. 注意服務選項,系統變量和系統狀態變量的區別(可以查看那個鏈接);有些只有選項沒有變量,有些只有變量沒有選項。
    • 其中的系統變量分爲全局和回話兩個級別,不論是全局性變量還是會話級變量都是用兩個@@符號表示(set的時候可以省略不加,詳細看help set),而用戶的自定義的變量前面用一個@符號表示,用select查詢它們的時候別忘了在前面加上(set的時候不能省略,也必須加上)
    • 不過要注意如果進行賦值set命令的話,系統變量的話直接寫系統變量名進行set設置即可,可省略@@等,帶一個@variable的代表着自定義的變量了。注意set和select使用變量的區別。
    • 注意選項中用下劃線或者橫線都行,但是變量中都是下劃線。
    • 利用show variables like的方式查看變量,大部分是以on off的形式顯示,但如果用select @@variable 的方式顯示系統變量,則大部分是以0,1 的方式顯示。
  2. 全局系統變量修改之後能夠影響到整個連接到服務器的用戶(所有的終端),而會話級別的只能影響這個會話的變量
    • 一個session會話就代表着一個連接終端連接是session的一個實現方式,終端一般指的是相對於服務器的連接的客戶端,終端通過會話建立連接後,才和服務器進行通訊,只要通信就有會話
    • 複習知識點:比如ssh服務,不論是SSH中的同一個應用端口號通過複製通道的方式再開啓多個session,還是直接新開一個客戶端和SSH通道連接到sshD服務器端,都是相當於新開了session.
    • ssh服務中,複製出來Xshell中的SSH的遠程的一個chanel連接,此時雖然應用端口號並沒有增加,但仍然是開啓了兩個session,兩個虛擬終端
    • 注意在ssh有個選項可以控制在相同的ssh通道內(也就是一個相同的應用端口號),能夠開啓的最多的session數量(其實這些session每一個都會被看做爲一個虛擬終端),在Xshell中這種就是複製ssh通道連接的方式。
    • 但是如果直接重新開一個ssh通道,相當於新開了一個SSH應用客戶端,增加了一個連接,應用端口號不同,這樣就不會受到這個選項的控制了(當然它這個通道也會受最大session的限制,這裏主要講的是這個選項限制的位置是在一個SSH通道內的最大連接數,而並非SSHD服務器的最大連接數)
    • 而在mysql中,就算是同一臺主機連接同一個mysql服務器,只要新開一個連接,這就算是不同的session了,(雖然表面上的終端仍舊是一個)。
    • 不過就算是全局系統變量,它修改之後也只能對新建立連接的用戶生效,已經連接上的用戶不會受到影響。
  3. 系統的狀態變量用show status [like 'variable'] 來查看,注意它和系統變量的區別:它只讀,不能用set命令修改。
  4. 重要注意點,結合之前的博客,可用mysqld --help -v 來查看mysql服務的主程序的參數都被設置成了哪些。
    • 經過測試得知,這個命令得到的各種變量的值就是根據mysql它的默認配置(safe中寫的配置)以及各種配置文件my.cnf中寫的配置文件來進行顯示的
    • 可見這個命令相當於是對mysql服務進行了一次僞啓動,並不真正的啓動它,但是能夠看到按照當前的配置如果啓動它,各個參數都是什麼
mysql --help
mysql -uroot -p123456 --socket=/data/mysql/3307/socket/mysql.sock
mysqladmin --help
mysqld_safe --help
mysqld --help -v
mysqld --help -v --socket=/data/mysql/3307/socket/mysql.sock 
=============
因爲這個是mysqld啓動服務器的選項,因此它和客戶端命令mysql 的直接可以用socket連接的方式不同。
在多實例中執行這個命令會看到,雖然socket指定的目標正確,但是datadir命令仍然不正確(因爲沒有載入多實例的my.cnf文件,它和mysql連接只需要寫socket不一樣)
因此下面的命令纔是最好的:
mysqld  --defaults-extra-file=/data/mysql/3307/etc/my.cnf --help -v
這裏不用寫socket了是因爲,這個配置文件中本來就寫的有socket選項。注意書寫的時候要把--help -v放在最後面,不然會報錯。
=============
  1. 一些變量
    skip_name_resolve=1 此選項的功能就是禁止將IP地址反向解析成名字,建議加到服務器的配置中去。
    • 它也有同名的系統變量,但是經過查看得知它是個只讀變量,無法更改,因此只能通過寫入配置文件或者說啓動服務器的時候加上--skip_name_resolve=1 的參數的方式(注意是加在safe腳本後面,也可以加在mysqld後面,不過一般不這麼做)更改。
      max_connections 全局變量,它控制着最大同時能併發連接到數據庫的主機個數。此變量是動態dynamic變量,可以直接在mysql數據庫中進行設置並生效,無需重啓mysqld服務。
      還有一些關於threads的狀態變量,以及com_select,com_insert等狀態變量(它們分別表示着自從數據庫上次啓動後,對當前對數據庫共進行了多少次select查詢操作,insert操作等,除了這些還有有其他一系列com統計操作總數的變量)
    • 注意狀態變量只能用show status命令查看到,用show variables 命令無法查看。
  2. sql_mode,這個變量比較特殊,它的值會影響到當前數據庫的一些模式。
    • 比如說把它設置爲traditional,則在字符串類型char中的字段數據,如果輸入的數據的長度多於定義字段時這個字符串char字段所允許的最大長度,此種語法將不被允許; 而如果不設置它之前,則是把超過最大長度後面的字符串給截斷,把前面的內容存儲到字段的記錄中去。
    • 注意traditional是多個sql_mode的集合,並非是一個。
    • 最新版本mariadb默認的嚴格模式,如果表的字符集不是utf8格式(比如默認成了latin1格式),則無法把中文輸入到這個表中的char類型字段中。
    • 在mysql數據庫中可以直接用set命令修改這個變量,注意它默認是一個global變量。注意global修改之後需要重新開啓終端連接纔會生效
    • 經過研究測試得知,sql_mode即是全局變量又是session變量(之前一直的錯誤認知主要就是把一個@當做了系統變量中的session變量的表示,實際上一個@variable就是代表着自定義的變量,不會代表系統變量,系統變量不論是什麼都用@@表示,或者省略它)
    • Setting the GLOBAL variable requires the SYSTEM_VARIABLES_ADMIN or SUPER privilege and affects the operation of all clients that connect from that time on. Setting the SESSION variable affects only the current client. Each client can change its session sql_mode value at any time.
    • 可見global設置之後要重新連接才能生效。
    • 注意,global變量用set設置的時候前面必須要加上global關鍵字,session可以不用加,這個無一例外
    • 更詳細的查看它的官方文檔幫助
  3. 接着6,可見session的優先級比global要高,而且自己隨時可以更改sessson變量的值,直接生效,退出連接再次連接就會失效;(猜想如果一個變量既有global又有session的話,登陸連接上數據庫之後,這個session的值其實就是從global繼承的,只不過每個session都可自己設置,更方便)。
    • 而global更改之後必須重連數據庫生效(當然服務器重啓的話不論是global還是session都恢復配置文件以及默認設置)。
    • 需要注意的一點就是show [global |session ] variables 或 status ; select [@@global | @@session.] variable ; set [ global | session ] system_var=value 等命令默認都是顯示或者設置的session變量(因爲它優先級高,且省略不寫global顯示的就是session),如果想要顯示或者設置global變量,則前面必須加上global關鍵字。(這裏注意set設置系統變量時@@也可省略,select不可,別總是弄混淆!)
    • 命令select [global] variables\G;測試,global變量數量比session的要少,說明有些系統變量只有session沒有global。不過show status狀態變量在目前最新版mariadb中global和session數量一致,,但不代表它倆就沒區別了,還是有區別的

附加:利用mysql的額外架構方式:

image

查詢緩存

查詢的執行過程

image

它利用不同數據庫的自己的通訊協議,客戶端連接服務器端,先查詢緩存,如果沒有再查詢數據庫(並將結果存入緩存),以實現加速查詢(和下次查詢相同目標加速)的效果。

  1. 查詢緩存( Query Cache )原理
    緩存SELECT操作或預處理查詢的結果集和SQL語句,當有新的SELECT語句或預處理查詢語句請求,先去查詢緩存,判斷是否存在可用的記錄集,判斷標準:與緩存的SQL語句,是否完全一樣,區分大小寫
  2. 優缺點
    不需要對SQL語句做任何解析和執行,當然語法解析必須通過在先,這裏不解析指的是命令的執行
    直接從Query Cache中獲得查詢結果,提高查詢性能
    查詢緩存的判斷規則,不夠智能,也即提高了查詢緩存的使用門檻,降低其效率;
    查詢緩存的使用,會增加檢查和清理Query Cache中記錄集的開銷
  3. 哪些查詢可能不會被緩存
    • 查詢語句中加了SQL_NO_CACHE參數(這個是select語句的參數)
    • 查詢語句中含有獲得值的函數,包含自定義函數,如:NOW() CURDATE()、 GET_LOCK()、 RAND()、 CONVERT_TZ()等
    • 對系統數據庫的查詢:mysql、 information_schema
    • 查詢語句中使用SESSION級別變量或存儲過程中的局部變量
    • 查詢語句中使用了LOCK IN SHARE MODE、 FOR UPDATE的語句
    • 查詢語句中類似SELECT …INTO 導出數據的語句
    • 對臨時表的查詢操作;存在警告信息的查詢語句;不涉及任何表或視圖的查詢語句;某用戶只有列級別權限的查詢語句
    • 事務隔離級別爲Serializable時,所有查詢語句都不能緩存
  4. 查詢緩存相關的服務器系統變量(注意在variable中他們都是以byte單位顯示的
    query_cache_min_res_unit:它表示在內存中給查詢緩存中分配的最小分配單位(內存塊),默認4k,較小值會減少浪費,但會導致更頻繁的內存分配操作,較大值會帶來浪費,會導致碎片過多,內存不足
    query_cache_limit:單個查詢結果能緩存的最大值,默認爲1M,對於查詢結果過大而無法緩存的語句,建議使用SQL_NO_CACHE參數
    query_cache_size:查詢緩存總共可用的內存空間;單位字節,必須是1024的整數倍,最小值40KB,低於此值有警報
    query_cache_wlock_invalidate:如果某表被其它的會話鎖定,是否仍然可以從查詢緩存中返回結果,默認值爲OFF,表示可以在表被其它會話鎖定的場景中繼續從緩存返回數據;ON則表示不允許
    query_cache_type:是否開啓緩存功能,取值爲ON, OFF, DEMAND
  5. SELECT語句的緩存控制
    SQL_CACHE:顯式指定存儲查詢結果於緩存之中
    SQL_NO_CACHE:顯式查詢結果不予緩存
    query_cache_type參數變量
    query_cache_type的值爲OFF或0時,查詢緩存功能關閉
    query_cache_type的值爲ON或1時,查詢緩存功能打開,SELECT的結果符合緩存條件即會緩存,否則,不予緩存,顯式指定SQL_NO_CACHE,不予緩存,此爲默認值
    query_cache_type的值爲DEMAND或2時,查詢緩存功能按需進行,顯式指定SQL_CACHE的SELECT語句纔會緩存;其它均不予緩存
  6. 查詢緩存相關的狀態變量:SHOW GLOBAL STATUS LIKE ‘Qcache%';
    Qcache_free_blocks:處於空閒狀態 Query Cache中內存 Block 數
    Qcache_total_blocks:Query Cache 中緩存所佔的總Block數量 ,當Qcache_free_blocks相對此值較大時,可能用內存碎片,執行FLUSH QUERY CACHE清理碎片
    Qcache_free_memory:處於空閒狀態的 Query Cache 內存總量
    Qcache_hits:Query Cache 命中次數
    Qcache_inserts:向 Query Cache 中插入新的 Query Cache的次數,即沒有命中的次數
    Qcache_lowmem_prunes:記錄因爲內存不足而被移除出查詢緩存的查詢數(LRU算法:最近最少使用)
    Qcache_not_cached:沒有被 Cache 的 SQL 數,包括無法被 Cache 的 SQL以及由於 query_cache_type 設置的不會被 Cache 的 SQL語句
    Qcache_queries_in_cache:在 Query Cache 中的 SQL 數量

命中率和內存使用率估算

注:此項有問題,說明查詢緩存內部還是進行過優化的:查詢緩存中內存塊的最小分配單位query_cache_min_res_unit :
(query_cache_size - Qcache_free_memory) / Qcache_queries_in_cache
查詢緩存命中率 :
Qcache_hits / ( Qcache_hits + Qcache_inserts ) 100%
查詢緩存內存使用率:
(query_cache_size – qcache_free_memory) /query_cache_size
100%

優化查詢緩存

image

其中需要解釋的點:
頻繁發生驗證工作:驗證工作就是驗證這個緩存是否還有效。比如緩存的結果放到內存查詢緩存區之後,是否把這個緩存的結果的數據(比如說一張表)在數據庫中進行了頻繁修改。如果這樣的話這個緩存的結果必然就無效了,需要重新緩存,這就造成命中率低。

InnoDB存儲引擎的緩存

InnoDB存儲引擎的緩衝池:
通常InnoDB存儲引擎緩衝池的命中不應該小於99%
查看相關狀態變量:
show global status like 'innodb%read%'\G

  • Innodb_buffer_pool_reads: 表示從物理磁盤讀取頁的次數
  • Innodb_buffer_pool_read_ahead: 預讀的次數
  • Innodb_buffer_pool_read_ahead_evicted:預讀頁,但是沒有讀取就從緩衝池中被替換的頁數量,一般用來判斷預讀的效率
  • Innodb_buffer_pool_read_requests: 從緩衝池中讀取頁次數
  • Innodb_data_read: 總共讀入的字節數
  • Innodb_data_reads:發起讀取請求的次數,每次讀取可能需要讀取多個頁

Innodb緩衝池命中率計算:

image

平均每次讀取的字節數:

image

注意點4(查詢緩存):

  1. 因爲查詢緩存是基於HASH值來存放的,也就是說把客戶的select查詢命令的的SQL語句進行hash運算之後的結果給存儲到緩存中。如果用戶的select語句中(哪怕大小寫或者空格)稍有不同,就會被看作是不同的緩存語句(hash值不同),會被分別存儲爲兩條緩存記錄。
  2. 注意查詢緩存的內存中一條記錄存放的有兩部分內容:一個是查詢語句select的hash值,另外一部分就是這個查詢語句對應的查詢結果。
  3. query_cache_size是一個global類型的dynamic變量,不過在runtime級別修改時不能加單位(M K等),只能用數值。只有在配置文件中寫入它的值的時候纔可以加上這些單位。
    • 注意Qcache_tital_blocks是目前緩存中總的已經是使用的blocks數量,它大於等於Qcache_queries_in_cache,因爲後者中的一個緩存記錄可能佔用多個blocks(在默認情況下也就是大於4096bytes時)
    • Qcache_free_memory是當前總的未使用的內存memory,並非query_cache_size

複習知識點

  • session代表一個會話,只要新開一個終端就開啓了一個新的會話,開啓一個新的連接,也是開啓了一個新的會話。更詳細看前面介紹。
  • session變量作用此session,runtime設置直接生效,關閉seession終端重連服務器便會失效;
  • global影響整個mysql服務器的所有終端,runtime修改時隻影響本次mysql服務器的runtime(注意需要重新開的終端session纔會生效,之前已經開啓的終端session的不變,因爲session變量優先級高,改global沒有改session不會影響它,除非同時修改session變量),mysql服務重啓(下一個runtime)global變量也會失效。
  • 只有配置文件my.cnf中設置的global 變量才能跨越runtime,每次服務啓動都生效。不過注意在這裏修改的話要重啓服務~

索引1

索引:
是特殊數據結構,定義在查找時作爲查找條件的字段,在MySQL又稱爲鍵key,索引通過存儲引擎實現
優點:
索引可以降低服務需要掃描的數據量,減少了IO次數
索引可以幫助服務器避免排序和使用臨時表
索引可以幫助將隨機I/O轉爲順序I/O
缺點:
佔用額外空間,影響插入速度
索引類型:
B+ TREE、 HASH、 R TREE
聚簇(集)索引、非聚簇索引:數據和索引是否存儲在一起
主鍵索引、二級(輔助)索引
稠密索引、稀疏索引:是否索引了每一個數據項
簡單索引、組合索引
左前綴索引:取前面的字符做索引
覆蓋索引:從索引中即可取出要查詢的數據,性能高

  1. 二叉樹:根,枝,葉的左右比重可能不同

image

  1. 紅黑樹:儘量讓葉子的比重平均

image

  1. B TREE

image

  1. B+ TREE

image

image

  • B+Tree索引:順序存儲,每一個葉子節點到根結點的距離是相同的;左前綴索引,適合查詢範圍類的數據
  • 可以使用B+Tree索引的查詢類型:
    全值匹配:精確所有索引列,如:姓zhang,名yinsheng,年齡24
    匹配最左前綴:即只使用索引的第一列,如:姓wang
    匹配列前綴:只匹配一列值開頭部分,如:姓以w開頭的
    匹配範圍值:如:姓duan和姓zhang之間
    精確匹配某一列並範圍匹配另一列:如:姓zhang,名以y開頭的
    只訪問索引的查詢
  • B+Tree索引的限制:
    如不從最左列開始,則無法使用索引,如:查找名爲yinsheng,或姓爲g結尾
    不能跳過索引中的列:如:查找姓zhang,年齡24的,只能使用索引第一列
  • 特別提示:
    索引列的順序和查詢語句的寫法應相匹配,才能更好的利用索引
    爲優化性能,可能需要針對相同的列但順序不同創建不同的索引來滿足不同類型的查詢需求
  1. Hash索引
    • Hash索引:基於哈希表實現,只有精確匹配索引中的所有列的查詢纔有效,索引自身只存儲索引列對應的哈希值和數據指針,索引結構緊湊,查詢性能好
    • Memory存儲引擎支持顯式hash索引,InnoDB和MyISAM存儲引擎不支持
    • 適用場景:只支持等值比較查詢,包括=, <=>, IN()
    • 不適合使用hash索引的場景:
      不適用於順序查詢:索引存儲順序的不是值的順序
      不支持模糊匹配
      不支持範圍查詢
      不支持部分索引列匹配查找:如A,B列索引,只查詢A列索引無效

6.1 空間數據索引R-Tree( Geospatial indexing )
MyISAM支持地理空間索引,可以使用任意維度組合查詢,使用特有的函數訪問,常用於做地理數據存儲,使用不多
InnoDB從MySQL5.7之後也開始支持
6.2 全文索引(FULLTEXT)
在文本中查找關鍵詞,而不是直接比較索引中的值,類似搜索引擎
InnoDB從MySQL 5.6之後也開始支持

  1. 聚簇和非聚簇索引

image

  • 外加主鍵和二級索引

image

冗餘和重複索引:
冗餘索引:(A),(A,B)
重複索引:已經有索引,再次建立索引
索引優化策略:

  1. 獨立地使用列:儘量避免其參與運算,獨立的列指索引列不能是表達式的一部分,也不能是函數的參數,在where條件中,始終將索引列單獨放在比較符號的一側
  2. 左前綴索引:構建指定索引字段的左側的字符數,要通過索引選擇性來評估
    • 索引選擇性:不重複的索引值和數據表的記錄總數的比值
  3. 多列索引:AND操作時更適合使用多列索引,而非爲每個列創建單獨的索引
  4. 選擇合適的索引列順序:無排序和分組時,將選擇性最高放左側

索引優化建議

只要列中含有NULL值,就最好不要在此例設置索引,複合索引如果有NULL值,此列在使用時也不會使用索引
儘量使用短索引,如果可以,應該制定一個前綴長度
對於經常在where子句使用的列,最好設置索引
對於有多個列where或者order by子句,應該建立複合索引
對於like語句,以%或者‘-’開頭的不會使用索引,以%結尾會使用索引
儘量不要在列上進行運算(函數操作和表達式操作)
儘量不要使用not in和<>操作

SQL語句性能優化

查詢時,能不要就不用,儘量寫全字段名
大部分情況連接效率遠大於子查詢
多表連接時,儘量小表驅動大表,即小表 join 大表
在有大量記錄的表分頁時使用limit
對於經常使用的查詢,可以開啓緩存
多使用explain和profile分析查詢語句
查看慢查詢日誌,找出執行時間長的sql語句優化

管理索引

  1. 創建索引:
    CREATE INDEX [UNIQUE] index_name ON tbl_name (index_col_name[(length)],...);
    ALTER TABLE tbl_name ADD INDEX index_name(index_col_name);
    help CREATE INDEX;
  2. 刪除索引:
    DROP INDEX index_name ON tbl_name;
    ALTER TABLE tbl_name DROP INDEX index_name(index_col_name);
  3. 查看索引:
    SHOW INDEXES FROM [db_name.]tbl_name;
    • 優化表空間:
      OPTIMIZE TABLE tb_name;
    • 查看索引的使用
      SET GLOBAL userstat=1;
      SHOW INDEX_STATISTICS;

EXPLAIN

  1. 通過EXPLAIN來分析索引的有效性
    EXPLAIN SELECT clause
    獲取查詢執行計劃信息,用來查看查詢優化器如何執行查詢
    輸出信息說明:
    參考 https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
  2. id: 當前查詢語句中,每個SELECT語句的編號
    複雜類型的查詢有三種:
    簡單子查詢
    用於FROM中的子查詢
    聯合查詢:UNION
    注意:UNION查詢的分析結果會出現一個額外匿名臨時表
  3. select_type:
    簡單查詢爲SIMPLE
    複雜查詢:
    SUBQUERY 簡單子查詢
    PRIMARY 最外面的SELECT
    DERIVED 用於FROM中的子查詢
    UNION UNION語句的第一個之後的SELECT語句
    UNION RESULT 匿名臨時表
  4. table:SELECT語句關聯到的表
  5. type:關聯類型或訪問類型,即MySQL決定的如何去查詢表中的行的方式,以下順序,性能從低到高
    • ALL: 全表掃描
    • index:根據索引的次序進行全表掃描;如果在Extra列出現“Using index”表示了使用覆蓋索引,而非全表掃描
    • range:有範圍限制的根據索引實現範圍掃描;掃描位置始於索引中的某一點,結束於另一點
    • ref: 根據索引返回表中匹配某單個值的所有行
    • eq_ref:僅返回一個行,但與需要額外與某個參考值做比較
    • const, system: 直接返回單個行
  6. possible_keys:查詢可能會用到的索引
  7. key: 查詢中使用到的索引
  8. key_len: 在索引使用的字節數
  9. ref: 在利用key字段所表示的索引完成查詢時所用的列或某常量值
  10. rows:MySQL估計爲找所有的目標行而需要讀取的行數
  11. Extra:額外信息
    Using index:MySQL將會使用覆蓋索引,以避免訪問表
    Using where:MySQL服務器將在存儲引擎檢索後,再進行一次過濾
    Using temporary:MySQL對結果排序時會使用臨時表
    Using filesort:對結果使用一個外部索引排序

注意點5(索引)

  1. 索引在大部分情況下會提高查詢效率(小部分就是比如說就在數據庫表中的最前面的幾條記錄,直接查詢更快),不過它會影響record插入(insert)的效率,因爲插入新數據之後還要更新一下它的索引(或者叫目錄)。
  2. B 樹是根據表中某一字段(比如ID號,主鍵)進行區間分段,然後再在分段的區間上繼續進行分段進行查詢數據的索引表的建立,並以此爲依據進行查詢的。
    • 它的優點是相對於二叉樹來說,分支變多但分級變少,容易查詢
    • 不過它的缺點是在索引表的各個節點上都包含有分段的ID這些分段邊界ID號的record的數據,會佔用很多空間且影響查詢速率,並且讓查詢的效率變得不同(比如說查詢一個分段邊界ID的數據和查詢不是分段邊界ID的數據,後者會比前者要多搜索一個分支級別);同時如果對數據進行部分連續範圍查詢的話,每次都要從根節點往下到葉子節點進行查詢,降低了查詢效率(因爲每個葉子節點之間沒有關聯)。
    • 因此目前都用的是B+ 樹的索引結構,它的所有的非葉子節點都只有指針信息(指向下一級的索引節點)而不包含數據,只有葉子結點纔有數據。同時每個葉子節點之間不僅包含有此部分葉子結點包含的所有record的數據(一個葉子結點包含多個record的數據,不要認爲只包含一個record),也包含有和它相連的下一個葉子結點的位置信息(指向下一個葉子節點)
    • 因此B+ 數進行索引查詢的時候,每個數據查詢的效率是一樣的(因爲都要到葉子結點的層級,索引節點不包含數據),同時它可以進行範圍搜索。同時它相比於B數來說,因爲索引節點沒有了數據,它更加的矮胖,查詢效率更高。
  3. 接2,B+樹是左前綴索引,順序存儲模式:它代表把一個字段創建索引之後,此索引是按照從左往右左前綴字符進行排序存儲的,並將每個記錄的真正的數據的位置的指針放到此索引表中對應的索引記錄上。
    • 當對這個索引進行查詢的時候,雖然說B+ 數是順序存儲的結果(按照數字ID或者字符排列順序,類似ls的排序,或者說字符集的排序順序),但是查詢的時候只能通過最左邊的匹配字符進行查詢,而不能跳過左邊的字符按照右邊的字符進行查詢。
    • 比如說 select from students where name like 'zhang%' 和
      select
      from students where name like '%yinsheng[%]' 的查詢方式,前者可以利用建立在name字段上的索引,而後者則相當於並沒有利用到此索引,和直接查詢沒有區別。因此要避免使用後者的查詢方式,也就是不能用跳過左邊的字符的查詢方式。
    • 同時,如果對兩個或多個字段建立B+ 複合索引,則根據建立的複合索引的查詢,也是要根據複合索引建立時的字段的先後順序進行查詢,它也是左前綴索引。因爲這個索引的建立過程就是從左往右的進行一個字段一個字段的排序,後面的字段沒有前面的字段優先級高(後面的字段的排序會受到前面的字段排序結果的限制,後面字段的排序只是對前面字段的排序的進一步排序和補充,只有前面的字段記錄內容相同的情況下,複合索引纔有意義,如果前面的字段每一個都不相同,則後面的字段進行復合索引的排序毫無意義)
    • 相同的字段,不同的建立複合索引的順序,則索引表的查詢也是不同的,要注意。先根據最左邊的字段進行查詢,然後進行下一個次左邊的字段進行查詢,以此類推。同時也要注意,在每個字段內查詢的時候也要左前綴查詢,上面已經介紹
  4. 從3中也可以看出,建立索引的字段要保證大部分的數據內容都不一樣,不然就沒有意義了。一個表的主鍵本身就是一個索引,叫做主鍵索引(創建主鍵的時候就已經自定定義它的字段爲索引了),而唯一鍵也是一個索引叫唯一鍵索引(注意它可以爲空,儘量不讓它爲空)
  5. InnoDB的聚簇索引排列:在建立表的時候,有且只能有一個(複合)主鍵,而這個主鍵索引的葉子節點上最終就放着這個表的主鍵的各個順序排列的值,以及其對應的每個記錄的數據信息。這些直接把數據放在葉子節點上的索引表就叫做聚簇索引。
    • 而這個聚簇索引的二級索引,比如說針對這個表的另外的非主鍵字段建立索引,則這個索引表的葉子節點上存放的內容是這個索引字段的順序排列的值,以及其相對應的主鍵的字段的值(注意和主鍵索引的區別)。通過這個索引查詢的時候,找到葉子節點之後還需去找主鍵索引進行二次查詢,最終才能找到全部的數據。
    • 由此也可以看出,一個表的主鍵只能有一個,不然這種聚簇索引存放方式將會導致數據存放的位置有多個重複的地方(如果能建立多個主鍵,則多個主鍵索引表的葉子節點上都存放數據,),浪費空間。
    • 簡單來說數據和索引放在一起就是聚簇索引,分開存放就是非聚簇索引。上面的二級索引就是非聚簇索引(不包含全部數據,只包含索引數據),主鍵索引就是聚簇索引(包含全部字段信息)。innodb引擎就是聚簇索引引擎,只有兩個數據文件。
    • MyIASM引擎就是非聚簇索引,有三個數據文件,它的主鍵索引和二級索引沒有很大的區別,都是非聚簇索引,因爲都不包含數據,都只包含指針指向真正的數據存放的位置。
  6. B+樹的葉子索引是稠密索引,上面的索引節點是稀疏索引。
    • 覆蓋索引就類似B樹的數據放在索引中的方式,可從索引出提取出數據。
  7. explain select語句:可以查看是否利用到了索引來進行這個select語句的查詢過程
    • 注意select命令在實際的執行過程中,它先利用查詢緩存進行緩存查詢,如果沒有匹配結果再進入表中利用可能的index索引查詢,如果沒有index則最後再直接遍歷查詢表進行查詢。
    • 用explain selece 語句 命令經過測試發現對於查詢緩存來說沒有任何影響,既不會增加hits,也不會增加inserts,同時也沒有被記錄到not-cached中,反正它的數據沒有任何變化。
    • 有些情況下即使建立了index表也有可能不用它來查詢,比如說要查詢的結果過多,佔了整個表的所有記錄的一大部分,則系統自動就進入表中去遍歷了,而不是使用index.
  8. 可在配置文件中設置userstat爲1, 重啓服務之後則mysql便可以記錄索引使用信息,利用show index_statistics 查看。
    • 如果發現某個索引使用很少或者幾乎不使用,可以刪除掉次索引用以節省空間。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章