MySQL全局優(yōu)化與Mysql 8.0新特性示例詳解
知識(shí)體系總覽
MySQL全局優(yōu)化與Mysql 8.0新特性 ├── 一、MySQL全局優(yōu)化總覽 │ └── ?1. 優(yōu)化優(yōu)先級(jí)(SQL索引 > 參數(shù)調(diào)優(yōu) > 硬件升級(jí)) ├── 二、MySQL Server系統(tǒng)參數(shù) │ ├── ?1. 連接相關(guān)(max_connections / max_user_connections / back_log) │ ├── ?2. 超時(shí)相關(guān)(wait_timeout / interactive_timeout) │ └── ?3. 查詢緩沖(sort_buffer_size / join_buffer_size) ├── 三、InnoDB引擎參數(shù) │ ├── ?1. 核心參數(shù)(innodb_thread_concurrency / innodb_buffer_pool_size) │ ├── ?2. 緩沖池命中率診斷 │ └── ?3. 鎖與日志相關(guān)(innodb_lock_wait_timeout / flush_log / sync_binlog) ├── 四、MySQL 8.0新特性詳解(16大特性) │ ├── ?1. 降序索引 → ?2. group by不再隱式排序 → ?3. 隱藏索引 │ ├── ?4. 函數(shù)索引 → ?5. SKIP LOCKED / NOWAIT → ?6. innodb_dedicated_server │ ├── ?7. 死鎖檢測(cè)開(kāi)關(guān) → ?8. undo獨(dú)立表空間 → ?9. binlog過(guò)期精確到秒 │ ├── ?10. 窗口函數(shù) → ?11. 默認(rèn)utf8mb4 → ?12. InnoDB系統(tǒng)表 │ ├── ?13. 元數(shù)據(jù)統(tǒng)一存儲(chǔ) → ?14. 自增主鍵持久化 → ?15. DDL原子化 │ └── ?16. 參數(shù)修改持久化(SET PERSIST)
一、MySQL全局優(yōu)化總覽

從上圖可以看出SQL及索引的優(yōu)化效果是最好的,而且成本最低,所以工作中我們要在這塊花更多時(shí)間。
補(bǔ)充一點(diǎn)配置文件my.ini或my.cnf的全局參數(shù):
假設(shè)服務(wù)器配置為:
- CPU:32核
- 內(nèi)存:64G
- DISK:2T SSD
下面參數(shù)都是服務(wù)端參數(shù),默認(rèn)在配置文件的 [mysqld] 標(biāo)簽下。
二、MySQL Server系統(tǒng)參數(shù)
參考文檔:https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_max_connections
1. max_connections
最大連接數(shù)。 連接的創(chuàng)建和銷毀都需要系統(tǒng)資源,比如內(nèi)存、文件句柄,業(yè)務(wù)說(shuō)的支持多少并發(fā),指的是每秒請(qǐng)求數(shù),也就是QPS。
max_connections=3000
一個(gè)連接最少占用內(nèi)存是256K,最大是64M,如果一個(gè)連接的請(qǐng)求數(shù)據(jù)超過(guò)64MB(比如排序),就會(huì)申請(qǐng)臨時(shí)空間,放到硬盤(pán)上。
如果3000個(gè)用戶同時(shí)連上mysql,最小需要內(nèi)存3000256KB=750M,最大需要內(nèi)存300064MB=192G。
如果innodb_buffer_pool_size是40GB,給操作系統(tǒng)分配4G,給連接使用的最大內(nèi)存不到20G,如果連接過(guò)多,使用的內(nèi)存超過(guò)20G,將會(huì)產(chǎn)生磁盤(pán)SWAP,此時(shí)將會(huì)影響性能。連接數(shù)過(guò)高,不一定帶來(lái)吞吐量的提高,而且可能占用更多的系統(tǒng)資源。
2. max_user_connections
允許用戶連接的最大數(shù)量,剩余連接數(shù)用作DBA管理。
max_user_connections=2980
3. back_log
MySQL能夠暫存的連接數(shù)量。如果MySQL的連接數(shù)達(dá)到max_connections時(shí),新的請(qǐng)求將會(huì)被存在堆棧中,等待某一連接釋放資源,該堆棧數(shù)量即back_log,如果等待連接的數(shù)量超過(guò)back_log,將被拒絕。
back_log=300
4. wait_timeout
指的是app應(yīng)用通過(guò)jdbc連接mysql進(jìn)行操作完畢后,空閑300秒后斷開(kāi),默認(rèn)是28800,單位秒,即8個(gè)小時(shí)。
wait_timeout=300
5. interactive_timeout
指的是mysql client連接mysql進(jìn)行操作完畢后,空閑300秒后斷開(kāi),默認(rèn)是28800,單位秒,即8個(gè)小時(shí)。
interactive_timeout=300
6. sort_buffer_size
每個(gè)需要排序的線程分配該大小的一個(gè)緩沖區(qū)。增加該值可以加速ORDER BY 或 GROUP BY操作。
sort_buffer_size=4M
sort_buffer_size是一個(gè)connection級(jí)的參數(shù),在每個(gè)connection(session)第一次需要使用這個(gè)buffer的時(shí)候,一次性分配設(shè)置的內(nèi)存。
注意: sort_buffer_size并不是越大越好,由于是connection級(jí)的參數(shù),過(guò)大的設(shè)置+高并發(fā)可能會(huì)耗盡系統(tǒng)的內(nèi)存資源。例如:500個(gè)連接將會(huì)消耗500*sort_buffer_size(4M)=2G。
7. join_buffer_size
用于表關(guān)聯(lián)緩存的大小,和sort_buffer_size一樣,該參數(shù)對(duì)應(yīng)的分配內(nèi)存也是每個(gè)連接獨(dú)享。
join_buffer_size=4M
三、InnoDB引擎參數(shù)
參考文檔:https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size
1. innodb_thread_concurrency
此參數(shù)用來(lái)設(shè)置innodb線程的并發(fā)數(shù),默認(rèn)值為0表示不被限制,若要設(shè)置則與服務(wù)器的CPU核心數(shù)相同或是CPU的核心數(shù)的2倍,如果超過(guò)配置并發(fā)數(shù),則需要排隊(duì),這個(gè)值不宜太大,不然可能會(huì)導(dǎo)致線程之間鎖爭(zhēng)用嚴(yán)重,影響性能。
innodb_thread_concurrency=64
2. innodb_buffer_pool_size
innodb存儲(chǔ)引擎buffer pool緩存大小,一般為物理內(nèi)存的60%-70%。
內(nèi)存大小直接反應(yīng)數(shù)據(jù)庫(kù)的性能。
innodb_buffer_pool_size=40G

如何判斷當(dāng)前數(shù)據(jù)庫(kù)的內(nèi)存是否已經(jīng)達(dá)到瓶頸了呢?
可以通過(guò)查看當(dāng)前服務(wù)器的狀態(tài),比較物理磁盤(pán)的讀取和內(nèi)存讀取的比例來(lái)判斷緩沖池的命中率,通常InnoDB存儲(chǔ)引擎的緩沖池的命中率不應(yīng)該小于99%,如:
show global status like 'innodb%read%'\G;
當(dāng)前服務(wù)器的狀態(tài)參數(shù):
- Innodb_buffer_pool_reads: 表示從物理磁盤(pán)讀取頁(yè)的次數(shù)
- Innodb_buffer_pool_read_ahead: 預(yù)讀的次數(shù)
- Innodb_buffer_pool_read_ahead_evicted: 預(yù)讀的頁(yè),但是沒(méi)有被讀取就從緩沖池中被替換的頁(yè)的數(shù)量,一般用來(lái)判斷預(yù)讀的效率
- Innodb_buffer_pool_read_requests: 從緩沖池中讀取頁(yè)的次數(shù)
- Innodb_data_reads: 總共讀入的字節(jié)數(shù)
- Innodb_data_reads: 發(fā)起讀取請(qǐng)求的次數(shù),每次讀取可能需要讀取多個(gè)頁(yè)
以下公式可以計(jì)算各種對(duì)緩沖池的操作:

緩沖池命中率計(jì)算公式:
命中率 = (Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100%
?? 面試要點(diǎn): 緩沖池命中率是判斷內(nèi)存瓶頸的核心指標(biāo),通常應(yīng) ≥ 99%。若低于99%說(shuō)明物理磁盤(pán)讀取過(guò)多,需要擴(kuò)大buffer_pool或排查SQL問(wèn)題。
3. innodb_lock_wait_timeout
行鎖鎖定時(shí)間,默認(rèn)50s,根據(jù)公司業(yè)務(wù)定,沒(méi)有標(biāo)準(zhǔn)值。
innodb_lock_wait_timeout=10
4. innodb_flush_log_at_trx_commit
參看上一節(jié)課內(nèi)容(redo log寫(xiě)入策略)。
innodb_flush_log_at_trx_commit=1
5. sync_binlog
參看上一節(jié)課內(nèi)容(binlog寫(xiě)入策略)。
sync_binlog=1
參考文檔:https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html
四、MySQL 8.0新特性詳解
建議使用8.0.17及之后的版本,更新的內(nèi)容比較多。
參考文檔:
- 添加棄用和刪除的特性:https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html
- 添加棄用和刪除的參數(shù):https://dev.mysql.com/doc/refman/8.0/en/added-deprecated-removed.html
- MySQL8 InnoDB架構(gòu):https://dev.mysql.com/doc/refman/8.0/en/innodb-architecture.html
1. 新增降序索引
MySQL在語(yǔ)法上很早就已經(jīng)支持降序索引,但實(shí)際上創(chuàng)建的仍然是升序索引,如下MySQL 5.7 所示,c2字段降序,但是從show create table看c2仍然是升序。8.0可以看到,c2字段降序。只有Innodb存儲(chǔ)引擎支持降序索引。
# ====MySQL 5.7演示====
mysql> create table t1(c1 int,c2 int,index idx_c1_c2(c1,c2 desc));
Query OK, 0 rows affected (0.04 sec)
mysql> insert into t1 (c1,c2) values(1, 10),(2,50),(3,50),(4,100),(5,80);
Query OK, 5 rows affected (0.02 sec)
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
KEY `idx_c1_c2` (`c1`,`c2`) --注意這里,c2字段是升序
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> explain select * from t1 order by c1,c2 desc; --5.7也會(huì)使用索引,但是Extra字段里有filesort文件排序
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 1 | 100.00 | Using index; Using filesort |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-----------------------------+
1 row in set, 1 warning (0.01 sec)
# ====MySQL 8.0演示====
mysql> create table t1(c1 int,c2 int,index idx_c1_c2(c1,c2 desc));
Query OK, 0 rows affected (0.02 sec)
mysql> insert into t1 (c1,c2) values(1, 10),(2,50),(3,50),(4,100),(5,80);
Query OK, 5 rows affected (0.02 sec)
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`c1` int DEFAULT NULL,
`c2` int DEFAULT NULL,
KEY `idx_c1_c2` (`c1`,`c2` DESC) --注意這里的區(qū)別,降序索引生效了
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
mysql> explain select * from t1 order by c1,c2 desc; --Extra字段里沒(méi)有filesort文件排序,充分利用了降序索引
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 1 | 100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t1 order by c1 desc,c2; --Extra字段里有Backward index scan,意思是反向掃描索引;
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+----------------------------------+
| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 1 | 100.00 | Backward index scan; Using index |
+----+-------------+-------+------------+-------+---------------+-----------+---------+------+------+----------+----------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t1 order by c1 desc,c2 desc; --Extra字段里有filesort文件排序,排序必須按照每個(gè)字段定義的排序或按相反順序才能充分利用索引
mysql> explain select * from t1 order by c1,c2; --Extra字段里有filesort文件排序
?? 關(guān)鍵理解: 降序索引使得ORDER BY中的ASC/DESC混合排序也能充分利用索引,避免filesort。注意排序字段必須按索引定義的順序或完全相反的順序,才能充分利用索引。
2. group by 不再隱式排序
mysql 8.0 對(duì)于group by 字段不再隱式排序,如需要排序,必須顯式加上order by 子句。
# ====MySQL 5.7演示====(group by默認(rèn)排序) mysql> select count(*),c2 from t1 group by c2; +----------+------+ | count(*) | c2 | +----------+------+ | 1 | 10 | | 2 | 50 | | 1 | 80 | | 1 | 100 | +----------+------+ 4 rows in set (0.00 sec) # ====MySQL 8.0演示====(group by不再默認(rèn)排序) mysql> select count(*),c2 from t1 group by c2; --8.0版本group by不再默認(rèn)排序 +----------+------+ | count(*) | c2 | +----------+------+ | 1 | 10 | | 2 | 50 | | 1 | 100 | | 1 | 80 | +----------+------+ 4 rows in set (0.00 sec) mysql> select count(*),c2 from t1 group by c2 order by c2; --需要自己加order by +----------+------+ | count(*) | c2 | +----------+------+ | 1 | 10 | | 2 | 50 | | 1 | 80 | | 1 | 100 | +----------+------+ 4 rows in set (0.00 sec)
3. 增加隱藏索引
使用 invisible 關(guān)鍵字在創(chuàng)建表或者進(jìn)行表變更中設(shè)置索引為隱藏索引。索引隱藏只是不可見(jiàn),但是數(shù)據(jù)庫(kù)后臺(tái)還是會(huì)維護(hù)隱藏索引的,在查詢時(shí)優(yōu)化器不使用該索引,即使用force index,優(yōu)化器也不會(huì)使用該索引,同時(shí)優(yōu)化器也不會(huì)報(bào)索引不存在的錯(cuò)誤,因?yàn)樗饕匀徽鎸?shí)存在,必要時(shí),也可以把隱藏索引快速恢復(fù)成可見(jiàn)。注意,主鍵不能設(shè)置為 invisible。
軟刪除就可以使用隱藏索引,比如我們覺(jué)得某個(gè)索引沒(méi)用了,刪除后發(fā)現(xiàn)這個(gè)索引在某些時(shí)候還是有用的,于是又得把這個(gè)索引加回來(lái),如果表數(shù)據(jù)量很大的話,這種操作耗費(fèi)時(shí)間是很多的,成本很高,這時(shí),我們可以將索引先設(shè)置為隱藏索引,等到真的確認(rèn)索引沒(méi)用了再刪除。
# 創(chuàng)建t2表,里面的c2字段為隱藏索引
mysql> create table t2(c1 int, c2 int, index idx_c1(c1), index idx_c2(c2) invisible);
Query OK, 0 rows affected (0.02 sec)
mysql> show index from t2\G
*************************** 1. row ***************************
Table: t2
Non_unique: 1
Key_name: idx_c1
Seq_in_index: 1
Column_name: c1
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: t2
Non_unique: 1
Key_name: idx_c2
Seq_in_index: 1
Column_name: c2
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: NO --隱藏索引不可見(jiàn)
Expression: NULL
2 rows in set (0.00 sec)
mysql> explain select * from t2 where c1=1;
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t2 | NULL | ref | idx_c1 | idx_c1 | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t2 where c2=1; --隱藏索引c2不會(huì)被使用
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t2 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select @@optimizer_switch\G --查看各種參數(shù)
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
mysql> set session optimizer_switch="use_invisible_indexes=on"; ----在會(huì)話級(jí)別設(shè)置查詢優(yōu)化器可以看到隱藏索引
Query OK, 0 rows affected (0.00 sec)
mysql> select @@optimizer_switch\G
*************************** 1. row ***************************
@@optimizer_switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=on,skip_scan=on,hash_join=on
1 row in set (0.00 sec)
mysql> explain select * from t2 where c2=1;
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t2 | NULL | ref | idx_c2 | idx_c2 | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+--------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> alter table t2 alter index idx_c2 visible;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table t2 alter index idx_c2 invisible;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
4. 新增函數(shù)索引
之前我們知道,如果在查詢中加入了函數(shù),索引不生效,所以MySQL 8引入了函數(shù)索引,MySQL 8.0.13開(kāi)始支持在索引中使用函數(shù)(表達(dá)式)的值。
函數(shù)索引基于虛擬列功能實(shí)現(xiàn),在MySQL中相當(dāng)于新增了一個(gè)列,這個(gè)列會(huì)根據(jù)你的函數(shù)來(lái)進(jìn)行計(jì)算結(jié)果,然后使用函數(shù)索引的時(shí)候就會(huì)用這個(gè)計(jì)算后的列作為索引。
mysql> create table t3(c1 varchar(10),c2 varchar(10));
Query OK, 0 rows affected (0.02 sec)
mysql> create index idx_c1 on t3(c1); --創(chuàng)建普通索引
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index func_idx on t3((UPPER(c2))); --創(chuàng)建一個(gè)大寫(xiě)的函數(shù)索引
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from t3\G
*************************** 1. row ***************************
Table: t3
Non_unique: 1
Key_name: idx_c1
Seq_in_index: 1
Column_name: c1
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: t3
Non_unique: 1
Key_name: func_idx
Seq_in_index: 1
Column_name: NULL
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: upper(`c2`) --函數(shù)表達(dá)式
2 rows in set (0.00 sec)
mysql> explain select * from t3 where upper(c1)='ZHUGE';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | t3 | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from t3 where upper(c2)='ZHUGE'; --使用了函數(shù)索引
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
| 1 | SIMPLE | t3 | NULL | ref | func_idx | func_idx | 43 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
5. InnoDB存儲(chǔ)引擎select for update跳過(guò)鎖等待
對(duì)于 select … for share(8.0新增加查詢共享鎖的語(yǔ)法)或 select … for update, 在語(yǔ)句后面添加NOWAIT、SKIP LOCKED語(yǔ)法可以跳過(guò)鎖等待,或者跳過(guò)鎖定。
- 在5.7及之前的版本,select…for update,如果獲取不到鎖,會(huì)一直等待,直到innodb_lock_wait_timeout超時(shí)。
- 在8.0版本,通過(guò)添加nowait,skip locked語(yǔ)法,能夠立即返回。如果查詢的行已經(jīng)加鎖,那么nowait會(huì)立即報(bào)錯(cuò)返回,而skip locked也會(huì)立即返回,只是返回的結(jié)果中不包含被鎖定的行。
應(yīng)用場(chǎng)景: 比如查詢余票記錄,如果某些記錄已經(jīng)被鎖定,用skip locked可以跳過(guò)被鎖定的記錄,只返回沒(méi)有鎖定的記錄,提高系統(tǒng)性能。
# 先打開(kāi)一個(gè)session1:
mysql> select * from t1;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 10 |
| 2 | 50 |
| 3 | 50 |
| 4 | 100 |
| 5 | 80 |
+------+------+
5 rows in set (0.00 sec)
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t1 set c2 = 60 where c1 = 2; --鎖定第二條記錄
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
# 另外一個(gè)session2:
mysql> select * from t1 where c1 = 2 for update; --等待超時(shí)
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> select * from t1 where c1 = 2 for update nowait; --查詢立即返回
ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.
mysql> select * from t1 for update skip locked; --查詢立即返回,過(guò)濾掉了第二行記錄
+------+------+
| c1 | c2 |
+------+------+
| 1 | 10 |
| 3 | 50 |
| 4 | 100 |
| 5 | 80 |
+------+------+
4 rows in set (0.00 sec)
6. 新增innodb_dedicated_server自適應(yīng)參數(shù)
能夠讓InnoDB根據(jù)服務(wù)器上檢測(cè)到的內(nèi)存大小自動(dòng)配置innodb_buffer_pool_size,innodb_log_file_size等參數(shù),會(huì)盡可能多的占用系統(tǒng)可占用資源提升性能。解決非專業(yè)人員安裝數(shù)據(jù)庫(kù)后默認(rèn)初始化數(shù)據(jù)庫(kù)參數(shù)默認(rèn)值偏低的問(wèn)題,前提是服務(wù)器是專用來(lái)給MySQL數(shù)據(jù)庫(kù)的,如果還有其他軟件或者資源或者多實(shí)例MySQL使用,不建議開(kāi)啟該參數(shù),不然會(huì)影響其它程序。
mysql> show variables like '%innodb_dedicated_server%'; --默認(rèn)是OFF關(guān)閉,修改為ON打開(kāi) +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | innodb_dedicated_server | OFF | +-------------------------+-------+ 1 row in set (0.02 sec)
7. 死鎖檢查控制
MySQL 8.0 (MySQL 5.7.15)增加了一個(gè)新的動(dòng)態(tài)變量 innodb_deadlock_detect,用于控制系統(tǒng)是否執(zhí)行 InnoDB 死鎖檢查,默認(rèn)是打開(kāi)的。死鎖檢測(cè)會(huì)耗費(fèi)數(shù)據(jù)庫(kù)性能的,對(duì)于高并發(fā)的系統(tǒng),我們可以關(guān)閉死鎖檢測(cè)功能,提高系統(tǒng)性能。但是我們要確保系統(tǒng)極少情況會(huì)發(fā)生死鎖,同時(shí)要將鎖等待超時(shí)參數(shù)調(diào)小一點(diǎn),以防出現(xiàn)死鎖等待過(guò)久的情況。
mysql> show variables like '%innodb_deadlock_detect%'; --默認(rèn)是打開(kāi)的 +------------------------+-------+ | Variable_name | Value | +------------------------+-------+ | innodb_deadlock_detect | ON | +------------------------+-------+ 1 row in set, 1 warning (0.01 sec)
8. undo文件不再使用系統(tǒng)表空間
默認(rèn)創(chuàng)建2個(gè)UNDO表空間,不再使用系統(tǒng)表空間。


9. binlog日志過(guò)期時(shí)間精確到秒
之前是天,并且參數(shù)名稱發(fā)生變化。在8.0版本之前,binlog日志過(guò)期時(shí)間設(shè)置都是設(shè)置expire_logs_days參數(shù),而在8.0版本中,MySQL默認(rèn)使用binlog_expire_logs_seconds參數(shù)。

10. 窗口函數(shù)(Window Functions):也稱分析函數(shù)
從 MySQL 8.0 開(kāi)始,新增了一個(gè)叫窗口函數(shù)的概念,它可以用來(lái)實(shí)現(xiàn)若干新的查詢方式。窗口函數(shù)與 SUM()、COUNT() 這種分組聚合函數(shù)類似,在聚合函數(shù)后面加上over()就變成窗口函數(shù)了,在括號(hào)里可以加上partition by等分組關(guān)鍵字指定如何分組,窗口函數(shù)即便分組也不會(huì)將多行查詢結(jié)果合并為一行,而是將結(jié)果放回多行當(dāng)中,即窗口函數(shù)不需要再使用 GROUP BY。
# 創(chuàng)建一張賬戶余額表
CREATE TABLE `account_channel` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '姓名',
`channel` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '賬戶渠道',
`balance` int DEFAULT NULL COMMENT '余額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
# 插入一些示例數(shù)據(jù)
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('1', 'zhuge', 'wx', '100');
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('2', 'zhuge', 'alipay', '200');
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('3', 'zhuge', 'yinhang', '300');
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('4', 'lilei', 'wx', '200');
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('5', 'lilei', 'alipay', '100');
INSERT INTO `account_channel` (`id`, `name`, `channel`, `balance`) VALUES ('6', 'hanmeimei', 'wx', '500');
mysql> select * from account_channel;
+----+-----------+---------+---------+
| id | name | channel | balance |
+----+-----------+---------+---------+
| 1 | zhuge | wx | 100 |
| 2 | zhuge | alipay | 200 |
| 3 | zhuge | yinhang | 300 |
| 4 | lilei | wx | 200 |
| 5 | lilei | alipay | 100 |
| 6 | hanmeimei | wx | 500 |
+----+-----------+---------+---------+
6 rows in set (0.00 sec)
mysql> select name,sum(balance) from account_channel group by name;
+-----------+--------------+
| name | sum(balance) |
+-----------+--------------+
| zhuge | 600 |
| lilei | 300 |
| hanmeimei | 500 |
+-----------+--------------+
3 rows in set (0.00 sec)
# 在聚合函數(shù)后面加上over()就變成分析函數(shù)了,后面可以不用再加group by制定分組,因?yàn)樵趏ver里已經(jīng)用partition關(guān)鍵字指明了如何分組計(jì)算,這種可以保留原有表數(shù)據(jù)的結(jié)構(gòu),不會(huì)像分組聚合函數(shù)那樣每組只返回一條數(shù)據(jù)
mysql> select name,channel,balance,sum(balance) over(partition by name) as sum_balance from account_channel;
+-----------+---------+---------+-------------+
| name | channel | balance | sum_balance |
+-----------+---------+---------+-------------+
| hanmeimei | wx | 500 | 500 |
| lilei | wx | 200 | 300 |
| lilei | alipay | 100 | 300 |
| zhuge | wx | 100 | 600 |
| zhuge | alipay | 200 | 600 |
| zhuge | yinhang | 300 | 600 |
+-----------+---------+---------+-------------+
6 rows in set (0.00 sec)
mysql> select name,channel,balance,sum(balance) over(partition by name order by balance) as sum_balance from account_channel;
+-----------+---------+---------+-------------+
| name | channel | balance | sum_balance |
+-----------+---------+---------+-------------+
| hanmeimei | wx | 500 | 500 |
| lilei | alipay | 100 | 100 |
| lilei | wx | 200 | 300 |
| zhuge | wx | 100 | 100 |
| zhuge | alipay | 200 | 300 |
| zhuge | yinhang | 300 | 600 |
+-----------+---------+---------+-------------+
6 rows in set (0.00 sec)
# over()里如果不加條件,則默認(rèn)使用整個(gè)表的數(shù)據(jù)做運(yùn)算
mysql> select name,channel,balance,sum(balance) over() as sum_balance from account_channel;
+-----------+---------+---------+-------------+
| name | channel | balance | sum_balance |
+-----------+---------+---------+-------------+
| zhuge | wx | 100 | 1400 |
| zhuge | alipay | 200 | 1400 |
| zhuge | yinhang | 300 | 1400 |
| lilei | wx | 200 | 1400 |
| lilei | alipay | 100 | 1400 |
| hanmeimei | wx | 500 | 1400 |
+-----------+---------+---------+-------------+
6 rows in set (0.00 sec)
mysql> select name,channel,balance,avg(balance) over(partition by name) as avg_balance from account_channel;
+-----------+---------+---------+-------------+
| name | channel | balance | avg_balance |
+-----------+---------+---------+-------------+
| hanmeimei | wx | 500 | 500.0000 |
| lilei | wx | 200 | 150.0000 |
| lilei | alipay | 100 | 150.0000 |
| zhuge | wx | 100 | 200.0000 |
| zhuge | alipay | 200 | 200.0000 |
| zhuge | yinhang | 300 | 200.0000 |
+-----------+---------+---------+-------------+
6 rows in set (0.00 sec)
專用窗口函數(shù)分類:
- 序號(hào)函數(shù):ROW_NUMBER()、RANK()、DENSE_RANK()
- 分布函數(shù):PERCENT_RANK()、CUME_DIST()
- 前后函數(shù):LAG()、LEAD()
- 頭尾函數(shù):FIRST_VALUE()、LAST_VALUE()
- 其它函數(shù):NTH_VALUE()、NTILE()
# 按照balance字段排序,展示序號(hào) mysql> select name,channel,balance,row_number() over(order by balance) as row_number1 from account_channel; +-----------+---------+---------+-------------+ | name | channel | balance | row_number1 | +-----------+---------+---------+-------------+ | zhuge | wx | 100 | 1 | | lilei | alipay | 100 | 2 | | zhuge | alipay | 200 | 3 | | lilei | wx | 200 | 4 | | zhuge | yinhang | 300 | 5 | | hanmeimei | wx | 500 | 6 | +-----------+---------+---------+-------------+ 6 rows in set (0.00 sec) # 按照balance字段排序,first_value()選出排第一的余額 mysql> select name,channel,balance,first_value(balance) over(order by balance) as first1 from account_channel; +-----------+---------+---------+--------+ | name | channel | balance | first1 | +-----------+---------+---------+--------+ | zhuge | wx | 100 | 100 | | lilei | alipay | 100 | 100 | | zhuge | alipay | 200 | 100 | | lilei | wx | 200 | 100 | | zhuge | yinhang | 300 | 100 | | hanmeimei | wx | 500 | 100 | +-----------+---------+---------+--------+ 6 rows in set (0.01 sec)
11. 默認(rèn)字符集由latin1變?yōu)閡tf8mb4
在8.0版本之前,默認(rèn)字符集為latin1,utf8指向的是utf8mb3,8.0版本默認(rèn)字符集為utf8mb4,utf8默認(rèn)指向的也是utf8mb4。
12. MyISAM系統(tǒng)表全部換成InnoDB表
將系統(tǒng)表(mysql)和數(shù)據(jù)字典表全部改為InnoDB存儲(chǔ)引擎,默認(rèn)的MySQL實(shí)例將不包含MyISAM表,除非手動(dòng)創(chuàng)建MyISAM表。
13. 元數(shù)據(jù)存儲(chǔ)變動(dòng)
MySQL 8.0刪除了之前版本的元數(shù)據(jù)文件,例如表結(jié)構(gòu).frm等文件,全部集中放入mysql.ibd文件里??梢钥匆?jiàn)下圖test庫(kù)文件夾里已經(jīng)沒(méi)有了frm文件。


14. 自增變量持久化
在8.0之前的版本,自增主鍵AUTO_INCREMENT的值如果大于max(primary key)+1,在MySQL重啟后,會(huì)重置AUTO_INCREMENT=max(primary key)+1,這種現(xiàn)象在某些情況下會(huì)導(dǎo)致業(yè)務(wù)主鍵沖突或者其他難以發(fā)現(xiàn)的問(wèn)題。自增主鍵重啟重置的問(wèn)題很早就被發(fā)現(xiàn),一直到8.0才被解決,8.0版本將會(huì)對(duì)AUTO_INCREMENT值進(jìn)行持久化,MySQL重啟后,該值將不會(huì)改變。
# ====MySQL 5.7演示====
mysql> create table t(id int auto_increment primary key,c1 varchar(20));
Query OK, 0 rows affected (0.03 sec)
mysql> insert into t(c1) values('zhuge1'),('zhuge2'),('zhuge3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
| 3 | zhuge3 |
+----+--------+
3 rows in set (0.00 sec)
mysql> delete from t where id = 3;
Query OK, 1 row affected (0.01 sec)
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
+----+--------+
2 rows in set (0.00 sec)
mysql> exit;
Bye
# 重啟MySQL服務(wù),并重新連接MySQL
mysql> insert into t(c1) values('zhuge4');
Query OK, 1 row affected (0.01 sec)
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
| 3 | zhuge4 |
+----+--------+
3 rows in set (0.00 sec)
mysql> update t set id = 5 where c1 = 'zhuge1';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 2 | zhuge2 |
| 3 | zhuge4 |
| 5 | zhuge1 |
+----+--------+
3 rows in set (0.00 sec)
mysql> insert into t(c1) values('zhuge5');
Query OK, 1 row affected (0.01 sec)
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 2 | zhuge2 |
| 3 | zhuge4 |
| 4 | zhuge5 |
| 5 | zhuge1 |
+----+--------+
4 rows in set (0.00 sec)
mysql> insert into t(c1) values('zhuge6');
ERROR 1062 (23000): Duplicate entry '5' for key 'PRIMARY'
# ====MySQL 8.0演示====
mysql> create table t(id int auto_increment primary key,c1 varchar(20));
Query OK, 0 rows affected (0.02 sec)
mysql> insert into t(c1) values('zhuge1'),('zhuge2'),('zhuge3');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
| 3 | zhuge3 |
+----+--------+
3 rows in set (0.00 sec)
mysql> delete from t where id = 3;
Query OK, 1 row affected (0.01 sec)
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
+----+--------+
2 rows in set (0.00 sec)
mysql> exit;
Bye
[root@localhost ~]# service mysqld restart
Shutting down MySQL.... SUCCESS!
Starting MySQL... SUCCESS!
# 重新連接MySQL
mysql> insert into t(c1) values('zhuge4');
Query OK, 1 row affected (0.00 sec)
mysql> select * from t; --生成的id為4,不是3
+----+--------+
| id | c1 |
+----+--------+
| 1 | zhuge1 |
| 2 | zhuge2 |
| 4 | zhuge4 |
+----+--------+
3 rows in set (0.00 sec)
mysql> update t set id = 5 where c1 = 'zhuge1';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 2 | zhuge2 |
| 4 | zhuge4 |
| 5 | zhuge1 |
+----+--------+
3 rows in set (0.00 sec)
mysql> insert into t(c1) values('zhuge5');
Query OK, 1 row affected (0.00 sec)
mysql> select * from t;
+----+--------+
| id | c1 |
+----+--------+
| 2 | zhuge2 |
| 4 | zhuge4 |
| 5 | zhuge1 |
| 6 | zhuge5 |
+----+--------+
4 rows in set (0.00 sec)
?? 面試要點(diǎn): MySQL 5.7中自增ID重啟后會(huì)被重置為max(id)+1,這可能導(dǎo)致主鍵沖突。8.0將AUTO_INCREMENT持久化到redo log,徹底解決此問(wèn)題。
15. DDL原子化
InnoDB表的DDL支持事務(wù)完整性,要么成功要么回滾。
MySQL 8.0 開(kāi)始支持原子 DDL 操作,其中與表相關(guān)的原子 DDL 只支持 InnoDB 存儲(chǔ)引擎。一個(gè)原子 DDL 操作內(nèi)容包括:更新數(shù)據(jù)字典,存儲(chǔ)引擎層的操作,在 binlog 中記錄 DDL 操作。
支持與表相關(guān)的 DDL:數(shù)據(jù)庫(kù)、表空間、表、索引的 CREATE、ALTER、DROP 以及 TRUNCATE TABLE。支持的其它 DDL :存儲(chǔ)程序、觸發(fā)器、視圖、UDF 的 CREATE、DROP 以及ALTER 語(yǔ)句。支持賬戶管理相關(guān)的 DDL:用戶和角色的 CREATE、ALTER、DROP 以及適用的 RENAME等等。
# MySQL 5.7 mysql> drop table t1,t2; //刪除表報(bào)錯(cuò)不會(huì)回滾,t1表會(huì)被刪除 ERROR 1051 (42S02): Unknown table 'test.t2' mysql> show tables; -- t1表已經(jīng)不見(jiàn)了! # MySQL 8.0 mysql> drop table t1,t2; //刪除表報(bào)錯(cuò)會(huì)回滾,t1表依然還在 ERROR 1051 (42S02): Unknown table 'test.t2' mysql> show tables; -- t1表還在!
16. 參數(shù)修改持久化
MySQL 8.0版本支持在線修改全局參數(shù)并持久化,通過(guò)加上PERSIST關(guān)鍵字,可以將修改的參數(shù)持久化到新的配置文件(mysqld-auto.cnf)中,重啟MySQL時(shí),可以從該配置文件獲取到最新的配置參數(shù)。
set global 設(shè)置的變量參數(shù)在mysql重啟后會(huì)失效。
mysql> set persist innodb_lock_wait_timeout=25;
系統(tǒng)會(huì)在數(shù)據(jù)目錄下生成一個(gè)包含json格式的mysqld-auto.cnf 的文件:
{
"Version": 1,
"mysql_server": {
"innodb_lock_wait_timeout": {
"Value": "25",
"Metadata": {
"Timestamp": 1675290252103863,
"User": "root",
"Host": "localhost"
}
}
}
}
?? 關(guān)鍵理解:
SET PERSIST將參數(shù)修改持久化到mysqld-auto.cnf,重啟后仍然生效。該文件優(yōu)先級(jí)高于my.cnf。
總結(jié)
到此這篇關(guān)于MySQL全局優(yōu)化與Mysql 8.0新特性的文章就介紹到這了,更多相關(guān)MySQL全局優(yōu)化與8.0新特性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Win10下免安裝版MySQL8.0.16的安裝和配置教程圖解
這篇文章主要介紹了Win10下免安裝版MySQL8.0.16的安裝和配置 ,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),具有一定的參考解決價(jià)值,需要的朋友可以參考下2019-06-06
MySQL如何統(tǒng)計(jì)一個(gè)數(shù)據(jù)庫(kù)所有表的數(shù)據(jù)量
最近在做統(tǒng)計(jì)想查找一個(gè)數(shù)據(jù)庫(kù)里基本所有的表數(shù)據(jù)量,下面這篇文章主要給大家介紹了關(guān)于MySQL如何統(tǒng)計(jì)一個(gè)數(shù)據(jù)庫(kù)所有表的數(shù)據(jù)量的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
MySQL數(shù)學(xué)函數(shù)簡(jiǎn)明總結(jié)
這篇文章主要介紹了MySQL數(shù)學(xué)函數(shù)簡(jiǎn)明總結(jié),本文總結(jié)了大多數(shù)常用的MySQL數(shù)學(xué)函數(shù),并給出了使用實(shí)例,需要的朋友可以參考下2014-09-09
一步步教你在Navicat上如何停止正在運(yùn)行的MYSQL語(yǔ)句
很多時(shí)候我們會(huì)提交一些耗時(shí)比較長(zhǎng)的sql,可能出現(xiàn)mysql服務(wù)器內(nèi)存或者CPU暴增,引起報(bào)警,甚至影響其他業(yè)務(wù),下面這篇文章主要給大家介紹了關(guān)于在Navicat上如何停止正在運(yùn)行的MYSQL語(yǔ)句的相關(guān)資料,需要的朋友可以參考下2023-03-03
MySQL的查詢緩存機(jī)制基本學(xué)習(xí)教程
這篇文章主要介紹了MySQL的查詢緩存機(jī)制基本學(xué)習(xí)教程,默認(rèn)針對(duì)InnoDB存儲(chǔ)引擎下來(lái)將,需要的朋友可以參考下2015-11-11
windows下mysql?8.0.27?安裝配置方法圖文教程
這篇文章主要為大家詳細(xì)介紹了windows下mysql?8.0.27?安裝配置方法圖文教程,文中安裝步驟介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
詳解MySQL導(dǎo)出指定表中的數(shù)據(jù)的實(shí)例
這篇文章主要介紹了詳解MySQL導(dǎo)出指定表中的數(shù)據(jù)的實(shí)例的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09

