文章目錄
數據結構-索引-實驗7:索引優化(MySQL-8.0)
一、實驗目的及要求
1、 理解索引優化的相關概念,如:基數、回表;
2、 掌握索引優化的規則;
二、實驗環境及相關情況(包含使用軟件、實驗設備、主要儀器及材料等)
1、實驗設備:
(1)微型計算機:i7處理器、2G內存
2、軟件系統:
(1)VMware Workstation 15 Player:虛擬機,用於安裝Windows 7操作系統。在虛擬機上安裝Windows 7,然後再安裝MySQL-8.0.13-winx64;
(2)Windows 7操作系統:
(3)MySQL-8.0.13-winx64:
(4)Navicat Premium 12:數據庫管理工具。
三、實驗內容
1、數據準備。
2、查看索引的使用情況 。
3、索引優化的相關概念。
4、索引優化的規則。
四、實驗步驟及結果(包含簡要的實驗步驟流程、結論陳述,可附頁)
(一)數據準備
1、新建表結構
-- 時間: 0s
SET FOREIGN_KEY_CHECKS=0;
-- 時間: 0s
DROP TABLE IF EXISTS `user_account`;
-- 時間: 0.054s
CREATE TABLE `user_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL COMMENT '賬號',
`password` varchar(50) DEFAULT NULL COMMENT '密碼',
`salt` int(11) DEFAULT 0 COMMENT '鹽值',
`sort` int(11) DEFAULT 0 COMMENT '排序',
`state` int(1) DEFAULT '1' COMMENT '狀態:0無效1有效',
`remark` VARCHAR(100) DEFAULT null COMMENT '備註',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;
-- 時間: 0s
SET FOREIGN_KEY_CHECKS=1;
2、檢查
(1)表結構
desc user_account ;
MySQL-5.6:
說明:
MySQL-8.0:
說明:
(2)索引
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(3)表信息
show table status from test where name='user_account';
MySQL-5.6:
MySQL-8.0
4、插入基礎數據
-- Affected rows: 1 時間: 0.521s
INSERT INTO `user_account` VALUES (null, '1', '1', 1, 1, 1, '備註');
-- Affected rows: 1 時間: 0.018s
INSERT INTO `user_account` VALUES (null, '2', '2', 2, 2, 1, '備註');
-- Affected rows: 1 時間: 0.016s
INSERT INTO `user_account` VALUES (null, '3', '3', 3, 3, 1, '備註');
-- Affected rows: 1 時間: 0.051s
INSERT INTO `user_account` VALUES (null, '4', '4', 4, 4, 1, '備註');
-- Affected rows: 1 時間: 0.017s
INSERT INTO `user_account` VALUES (null, '5', '5', 5, 5, 0, '備註');
5、插入千萬級別數據
-- ------------------ MySQL-5.6
-- Affected rows: 5 時間: 0.001s
-- Affected rows: 10 時間: 0s
-- Affected rows: 20 時間: 0.001s
-- Affected rows: 40 時間: 0s
-- Affected rows: 80 時間: 0.001s
-- Affected rows: 160 時間: 0.001s
-- Affected rows: 320 時間: 0.003s
-- Affected rows: 640 時間: 0.003s
-- Affected rows: 1280 時間: 0.007s
-- Affected rows: 2560 時間: 0.015s
-- Affected rows: 5120 時間: 0.024s
-- Affected rows: 10240 時間: 0.044s
-- Affected rows: 20480 時間: 0.077s
-- Affected rows: 40960 時間: 0.181s
-- Affected rows: 81920 時間: 0.421s
-- Affected rows: 163840 時間: 0.74s
-- Affected rows: 327680 時間: 1.592s
-- Affected rows: 655360 時間: 2.406s
-- Affected rows: 1310720 時間: 5.578s
-- Affected rows: 2621440 時間: 9.642s
-- Affected rows: 5242880 時間: 17.658s
-- ------------------ MySQL-5.6
-- ------------------ MySQL-8.0
-- Affected rows: 5 時間: 0.066s
-- Affected rows: 10 時間: 0.04s
-- Affected rows: 20 時間: 0.056s
-- Affected rows: 40 時間: 0.025s
-- Affected rows: 80 時間: 0.018s
-- Affected rows: 160 時間: 0.014s
-- Affected rows: 320 時間: 0.095s
-- Affected rows: 640 時間: 0.077s
-- Affected rows: 1280 時間: 0.094s
-- Affected rows: 2560 時間: 0.304s
-- Affected rows: 5120 時間: 0.302s
-- Affected rows: 10240 時間: 0.959s
-- Affected rows: 20480 時間: 0.677s
-- Affected rows: 40960 時間: 1.481s
-- Affected rows: 81920 時間: 4.912s
-- Affected rows: 163840 時間: 6.598s
-- Affected rows: 327680 時間: 7.989s
-- Affected rows: 655360 時間: 11.656s
-- Affected rows: 1310720 時間: 16.586s
-- Affected rows: 2621440 時間: 34.713s
-- Affected rows: 5242880 時間: 86.015s
-- ------------------ MySQL-8.0
-- 第一步:執行21次,數據量大概爲10485760,爲千萬級別
INSERT into user_account SELECT null,t.username,t.`password`,t.salt,t.sort,t.state,t.remark FROM `user_account` t;
-- MySQL-5.6:數量:10485760 第一次查詢時間: 12.48s 第二次查詢時間: 1.968s 第三次查詢時間: 1.968s
-- MySQL-8.0:數量:10485760 第一次查詢時間: 9.837s 第二次查詢時間: 5.44s 第三次查詢時間: 3.592s 時間: 2.626s 時間: 2.634s 時間: 2.656s
SELECT count(*) FROM user_account t ;
-- 第二步:更新username、password、salt和sort
-- MySQL-5.6:Affected rows: 10485755 時間: 125.239s
-- MySQL-8.0:Affected rows: 10485755 時間: 479.257s
update user_account set `username` = id,`password` = id,`salt`=id,`sort`=id ;
-- 第三步:更新remark
-- MySQL-5.6:Affected rows: 943384 時間: 55.905s
-- MySQL-8.0:Affected rows: 943384 時間: 35.478s
update user_account set remark = null where id < 1139965;
-- MySQL-5.6:Affected rows: 1393115 時間: 60.492s
-- MySQL-8.0:Affected rows: 1393115 時間: 31.097s
update user_account set remark = null where id > 9485760;
SELECT * FROM user_account t limit 100;
6、索引建立與刪除SQL
後面的實驗環節會經常用到
ALTER TABLE user_account ADD INDEX index_user_account_username (username);
ALTER TABLE user_account ADD INDEX index_user_account_salt (salt);
ALTER TABLE user_account ADD INDEX index_user_account_sort (sort);
ALTER TABLE user_account ADD INDEX index_user_account_state (state);
ALTER TABLE user_account ADD INDEX index_user_account_remark (remark);
ALTER TABLE user_account ADD INDEX index_user_account_username_password (username,password);
drop INDEX index_user_account_username on user_account ;
drop INDEX index_user_account_salt on user_account ;
drop INDEX index_user_account_sort on user_account ;
drop INDEX index_user_account_state on user_account ;
drop INDEX index_user_account_remark on user_account ;
drop INDEX index_user_account_username_password on user_account ;
SHOW INDEX FROM user_account;
(二)概念:基數
1、準備
(1)索引準備
-- MySQL-5.6:時間: 32.383s 時間: 25.747s
-- MySQL-8.0:時間: 60.349s
ALTER TABLE user_account ADD INDEX index_user_account_username (username);
-- MySQL-5.6:時間: 55.055s 時間: 19.067s
-- MySQL-8.0:時間: 51.075s
ALTER TABLE user_account ADD INDEX index_user_account_state (state);
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
2、含義
單個列唯一鍵(distict_keys)的數量叫做基數。執行以下SQL,結果如下圖所示:
-- MySQL-5.6:時間: 90.698s 時間: 26.843s
-- MySQL-8.0:時間: 79.946s 時間: 33.241s
SELECT COUNT(*),COUNT(DISTINCT username),COUNT(DISTINCT state) FROM user_account;
MySQL-5.6 and MySQL-8.0:
user_account表的總行數是10485760,username列的基數是10485760,state列的基數是2,說明state列裏面有大量重複值,username列的基數等於總行數,說明 username列沒有重複值,相當於主鍵。
3、測試
(1)先計算數量
-- MySQL-5.6:時間: 36.815s 時間: 1.649s
-- MySQL-8.0:時間: 10.367s
SELECT count(*) FROM user_account;
-- MySQL-5.6:時間: 6.979s
-- MySQL-8.0:第一次時間: 6.306s 第二次時間: 1.721s 第三次時間: 1.828s
SELECT count(*) FROM user_account t where t.state = 1;
-- MySQL-5.6:時間: 7.02s
-- MySQL-8.0:時間: 0.01s
SELECT count(*) FROM user_account t where t.username = '1';
總記錄數:10485760
state = 1的記錄數:8388608
username = '1’的記錄數:1
(2)對應的數據比例
比較項 | 基數 | 總數 | 比例 |
---|---|---|---|
state = 1 | 4 | 5 | 8388608/10485760*100%=80% |
username = ‘1’ | 1 | 5 | 1/5*100%=20% |
(3)測試點:2個
現在問題來了,假設state和username 列都有索引,那麼以下這兩個查詢
SELECT * FROM user_account t where t.state = 1;
SELECT * FROM user_account t where t.username = '1';
都能命中索引嗎?
第一個,where t.state = 1
EXPLAIN SELECT * FROM user_account t where t.state = 1;
當數據量很大時,會命中索引。如數據量很大,即使返回數據比例爲80%,但還是走了索引。
MySQL-5.6:
MySQL-8.0:
當數據量很小時,沒有命中索引。如當總記錄數爲5,where t.state = 1返回4條記錄時,沒有命中索引。
MySQL-5.6:
注意:
extended:執行Mysql的explain 的輸出會比單純的explain多一列filtered(MySQL 5.7缺省就會輸出filtered),它指返回結果的行佔需要讀到的行(rows列的值)的百分比。filtered是個非常有用的值。如這裏的80指的是80%。
第二個,where t.username = ‘1’
EXPLAIN SELECT * FROM user_account t where t.username = '1';
MySQL-5.6:
MySQL-8.0:
命中了索引index_user_account_username,因爲走索引直接就能找到要查詢的記錄,所以filtered的值爲100。這裏的100指的是100%。
4、小結
經測試,返回表中 8.805714%至10.871553% 的數據會走索引,返回超過這個比例數據就使用全表掃描。
估計這個比例只是一個大概的範圍,並不是絕對的比例。
詳見(五)索引優化的規則:8、範圍條件查詢可以命中索引
(四)查看索引的使用情況
有些時候雖然數據庫有索引,但是並不被優化器選擇使用。
SHOW STATUS LIKE 'Handler_read%';
MySQL-5.6:
MySQL-8.0:
參數名 | 說明 |
---|---|
Handler_read_key | 如果索引正在工作,Handler_read_key的值將很高。 |
Handler_read_rnd_next | 數據文件中讀取下一行的請求數。如果正在進行大量的表掃描,值將較高,則說明索引利用不理想。 |
(五)索引優化的規則:16條
1、返回數據的比例
返回數據的比例是重要的指標,比例越低越容易命中索引。
本實驗測試的數據表明,大概爲8%。
2、前導模糊查詢
(1)索引準備
由於當前已經對username建立了索引,故本測試點無需再建立索引。
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
疑問:爲什麼state的Cardinality爲1?
(2)測試點:2個
第一,前導模糊查詢不能命中索引:
EXPLAIN SELECT * FROM user_account t where t.username like '%10000%';
MySQL-5.6:
MySQL-8.0:
第二,非前導模糊查詢則可以使用索引,可優化爲使用非前導模糊查詢:
EXPLAIN SELECT * FROM user_account t where t.username like '10000%';
MySQL-5.6:
MySQL-8.0:
3、隱式轉換
數據類型出現隱式轉換的時候不會命中索引,特別是當列類型是字符串,一定要將字符常量值用引號引起來
(1)索引準備
由於當前已經對username建立了索引,故本測試點無需再建立索引。
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(2)測試點:2個
第一,出現隱式轉換的時候
EXPLAIN SELECT * FROM user_account t where t.username = 1;
MySQL-5.6:
MySQL-8.0:
第二,沒有出現隱式轉換的時候
EXPLAIN SELECT * FROM user_account t where t.username = '1';
MySQL-5.6:
MySQL-8.0:
4、最左原則
複合索引的情況下,查詢條件不包含索引列最左邊部分(不滿足最左原則),不會命中符合索引。
(1)索引準備
先暫時刪除username和password的索引,接着爲username、password列創建複合索引。
-- MySQL-5.6:時間: 0.088s
-- MySQL-8.0:時間: 0.302s
drop INDEX index_user_account_username on user_account ;
-- MySQL-5.6:時間: 0.015s
-- MySQL-8.0:時間: 0.042s
drop INDEX index_user_account_state on user_account ;
-- MySQL-5.6:時間: 66.24s
-- MySQL-8.0:時間: 64.255s
ALTER TABLE user_account ADD INDEX index_user_account_username_password (username,password);
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點:3個
第一,根據最左原則,where條件的username,是複合索引index_user_account_username_password中的最左列,所以可以命中:
EXPLAIN SELECT * FROM user_account WHERE username='2' AND state=1;
MySQL-5.6:
MySQL-8.0:
第二,注意:最左原則並不是說是查詢條件的順序。現在交換一下查詢條件中的username和state:
EXPLAIN SELECT * FROM user_account WHERE state=1 AND username='2';
MySQL-5.6:
MySQL-8.0:
第三,而是查詢條件中是否包含索引最左列字段:下面沒有包含最左列username,只要password,所以不會命中。
EXPLAIN SELECT * FROM user_account WHERE password = '2' AND state=1;
MySQL-5.6:
MySQL-8.0:
5、union、in、or
union、in、or 都能夠命中索引,建議使用 in。
(1)索引準備
-- MySQL-5.6:時間: 0.051s
-- MySQL-8.0:時間: 0.105s
drop INDEX index_user_account_username_password on user_account ;
-- MySQL-5.6:時間: 24.258s
-- MySQL-8.0:時間: 63.82s
ALTER TABLE user_account ADD INDEX index_user_account_username (username);
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點
第一,union:
EXPLAIN
SELECT * FROM user_account WHERE username = '1'
UNION ALL
SELECT * FROM user_account WHERE username = '2';
MySQL-5.6:
MySQL-8.0:
第二,in:
EXPLAIN SELECT * FROM user_account WHERE username IN ('1','2');
MySQL-5.6:
MySQL-8.0:
這裏看到會走索引。
但是,當數據量很小時,有可能不走索引。因爲即使有索引,in後面的值超過一定個數後,就會分析消耗,最後如果判斷出消耗時間比走全表掃描還多,則就不走索引。
第三,or:
EXPLAIN SELECT * FROM user_account WHERE username ='1' or username ='2';
MySQL-5.6:
MySQL-8.0:
查詢的CPU消耗:or > in >union
6、or前的條件中列有索引,而後面的列中沒有索引
用or分割開的條件,如果or前的條件中列有索引,而後面的列中沒有索引,那麼涉及到的索引都不會被用到。
(1)索引準備
由於當前已經對username建立了索引,故本測試點無需再建立索引。
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點
第一,or前的條件中列有索引,or後面的條件列中沒有索引
EXPLAIN SELECT * FROM user_account WHERE username ='1' or state = 1;
MySQL-5.6:
MySQL-8.0:
因爲or後面的條件列中沒有索引,那麼後面的查詢肯定要走全表掃描,
在存在全表掃描的情況下,就沒有必要多一次索引掃描增加IO訪問。
7、負向條件查詢:!=、<>、not in、not exists、not like
負向條件查詢不能使用索引,可以優化爲 in 查詢。
(1)索引準備
由於當前已經對username建立了索引,故本測試點無需再建立索引。
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點
負向條件有:!=、<>、not in、not exists、not like 等。
第一,負向條件不能命中緩存:
EXPLAIN SELECT * FROM user_account WHERE username !='1' AND username != '2';
MySQL-5.6:
MySQL-8.0:
第二,可以優化爲 in 查詢,但是前提是區分度要高,返回數據的比例在30%以內:
EXPLAIN SELECT * FROM user_account WHERE username IN ('1','3','4');
MySQL-5.6:
MySQL-8.0:
8、範圍條件查詢:<、<=、>、>=、between
範圍條件查詢可以命中索引
(1)索引準備
-- MySQL-5.6:時間: 0.057s
-- MySQL-8.0:時間: 0.132s
drop INDEX index_user_account_username on user_account ;
-- MySQL-5.6:時間: 50.27s
-- MySQL-8.0:時間: 50.981s
ALTER TABLE user_account ADD INDEX index_user_account_salt (salt);
-- MySQL-5.6:時間: 51.675s
-- MySQL-8.0:時間: 50.249s
ALTER TABLE user_account ADD INDEX index_user_account_sort (sort);
(2)user表索引詳情:
SHOW INDEX FROM user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點:6個
範圍條件有:<、<=、>、>=、between等
第一,範圍條件查詢可以命中索引:
EXPLAIN SELECT * FROM user_account WHERE sort < 699433;
MySQL-5.6:
MySQL-8.0:
第二,但是當數據量達到一定程度時,就不會走索引,而是會全表掃描了。
EXPLAIN SELECT * FROM user_account WHERE sort < 699434;
MySQL-5.6:
MySQL-8.0:
第三,使用<=時,數量上又略有不同。
EXPLAIN SELECT * FROM user_account WHERE sort <= 699431;
MySQL-5.6:
MySQL-8.0:
第四,但是當數據量達到一定程度時,就不會走索引,而是會全表掃描了。
EXPLAIN SELECT * FROM user_account WHERE sort <= 699432;
MySQL-5.6:
MySQL-8.0:
第五,範圍列可以用到索引(聯合索引必須是最左前綴),但是範圍列後面的列無法用到索引,索引最多用於一個範圍列,如果查詢條件中有兩個範圍列則無法全用到索引。也就是說,只能用其中一個索引,而且是選擇代價最小的作爲索引:
sort、salt都是索引。
EXPLAIN SELECT * FROM user_account WHERE salt < 100;
MySQL-5.6:
MySQL-8.0:
選擇代價最小的作爲索引,salt返回數量最少,代價最小。
EXPLAIN SELECT * FROM user_account WHERE sort < 699433 AND salt < 100;
MySQL-5.6:
MySQL-8.0:
即使交換順序,也是salt。
EXPLAIN SELECT * FROM user_account WHERE salt < 100 AND sort < 699433;
MySQL-5.6:
MySQL-8.0:
第六,如果是範圍查詢和等值查詢同時存在,優先匹配等值查詢列的索引:
EXPLAIN SELECT * FROM user_account WHERE salt < 100 AND sort = 1139965;
MySQL-5.6:
MySQL-8.0:
9、數據庫執行計算
(1)索引準備
由於當前已經對sort建立了索引,故本測試點無需再建立索引。
(2)user_account表索引詳情:
show index from user_account;
MySQL-5.6:
MySQL-8.0:
數據庫執行計算不會命中索引
(3)測試點
第一,執行計算:sort < 1000
EXPLAIN SELECT * FROM user_account WHERE sort < 1000;
MySQL-5.6:
MySQL-8.0:
第二,執行計算:sort+1 < 1000
EXPLAIN SELECT * FROM user_account WHERE sort+1 < 1000;
MySQL-5.6:
MySQL-8.0:
計算邏輯應該儘量放到業務層處理,節省數據庫的 CPU的同時最大限度的命中索引。
10、利用覆蓋索引進行查詢,避免回表
被查詢的列,數據能從索引中取得,而不用通過行定位符 row-locator 再到 row 上獲取,即“被查詢列要被所建的索引覆蓋”,這能夠加速查詢速度。
(1)索引準備
-- MySQL-5.6:時間: 0.03s
-- MySQL-8.0:時間: 0.105s
drop INDEX index_user_account_salt on user_account ;
-- MySQL-5.6:時間: 0.026s
-- MySQL-8.0:時間: 0.175s
drop INDEX index_user_account_sort on user_account ;
-- MySQL-5.6:時間: 65.232s
-- MySQL-8.0:時間: 67.54s
ALTER TABLE user_account ADD INDEX index_user_account_username (username);
(2)user表索引詳情:
SHOW INDEX FROM user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點
第一,因爲username字段是索引列,所以直接從索引中就可以獲取值,不必回表查詢:
EXPLAIN SELECT username FROM user_account WHERE username = '1000';
MySQL-5.6:
MySQL-8.0:
第二,當查詢其他列時,就需要回表查詢,這也是爲什麼要避免SELECT *
的原因之一:
EXPLAIN SELECT * FROM user_account WHERE username = '1000';
MySQL-5.6:
MySQL-8.0:
11、建立索引的列,不允許爲 null
(1)索引準備
-- MySQL-5.6:時間: 0.046s
-- MySQL-8.0:時間: 0.152s
drop INDEX index_user_account_username on user_account ;
-- MySQL-5.6:時間: 60.403s
-- MySQL-8.0:時間: 52.006s
ALTER TABLE user_account ADD INDEX index_user_account_remark (remark);
(2)user表索引詳情:
SHOW INDEX FROM user_account;
MySQL-5.6:
MySQL-8.0:
(3)測試點
單列索引不存 null 值,複合索引不存全爲 null 的值,如果列允許爲 null,可能會得到“不符合預期”的結果集,所以,請使用 not null 約束以及默認值。
第一,IS NULL可以命中索引:
EXPLAIN SELECT * FROM user_account where remark IS NULL;
MySQL-5.6:
MySQL-8.0:
第二,IS NOT NULL不能命中索引:
EXPLAIN SELECT * FROM user_account where remark IS NOT NULL;
MySQL-5.6:
MySQL-8.0:
雖然IS NULL可以命中索引,但是NULL本身就不是一種好的數據庫設計,應該使用NOT NULL 約束以及默認值
12、更新十分頻繁的字段上不宜建立索引
因爲更新操作會變更B+樹,重建索引。這個過程是十分消耗數據庫性能的。
13、區分度不大的字段上不宜建立索引
類似於性別這種區分度不大的字段,建立索引的意義不大。因爲不能有效過濾數據,性能和全表掃描相當。
另外返回數據的比例在30%以外的情況下,優化器不會選擇使用索引。
疑問:比例30%從哪裏來?本實驗測試的數據表明,大概爲8%。
14、業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引
雖然唯一索引會影響insert速度,但是對於查詢的速度提升是非常明顯的。另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,在併發的情況下,依然有髒數據產生。
15、多表關聯時,要保證關聯字段上一定有索引
16、創建索引時避免以下錯誤觀念
- 索引越多越好,認爲一個查詢就需要建一個索引。
- 寧缺勿濫,認爲索引會消耗空間、嚴重拖慢更新和新增速度。
- 抵制唯一索引,認爲業務的唯一性一律需要在應用層通過“先查後插”方式解決。
- 過早優化,在不瞭解系統的情況下就開始優化。
五、實驗總結(包括心得體會、問題回答及實驗改進意見)
1、通過本次實驗,提高了SQL性能優化的意識。
2、瞭解了索引對SQL的性能影響很大。
3、學會了使用EXPLAIN命令分析一下SQL
4、理解了基數、回表的概念
5、掌握了索引優化的規則。
6、通過比較了MySQL-5.6和MySQL-8.0,懂得了要學會一門技術,除了學會基本原理,還要經常關注該技術的發展,瞭解不同版本的差異。
六、參考
《深入淺出MySQL》