數據結構-索引-實驗5:索引優化基礎

數據結構-索引-實驗5:索引優化基礎

一、實驗目的及要求

1、瞭解SQL優化的一般步驟

2、瞭解定期分析、檢查、優化表。

3、掌握常用 SQL 的優化。

二、實驗環境及相關情況(包含使用軟件、實驗設備、主要儀器及材料等)

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、常用 SQL 的優化。

四、實驗步驟及結果(包含簡要的實驗步驟流程、結論陳述,可附頁)

(一)數據準備

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 ;

在這裏插入圖片描述

(2)索引
show index from user_account;

在這裏插入圖片描述

(3)表信息
show table status from test where name='user_account';

在這裏插入圖片描述

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-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-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-8.0:Affected rows: 10485755 時間: 479.257s
update user_account set `username` = id,`password` = id,`salt`=id,`sort`=id ;

-- 第三步:更新remark
-- MySQL-8.0:Affected rows: 943384   時間: 35.478s
update user_account set remark = null where id < 1139965;

-- 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_username_password (username,password);

drop INDEX index_user_account_username on user_account ;
drop INDEX index_user_account_username_password on user_account ;

SHOW INDEX FROM user_account;

(二)一般步驟

1、第一,瞭解各種 SQL 的執行頻率

命令:show [session|global] status

show status;
show session status;
show global status;

MySQL 客戶端連接成功後,通過【 show [session|global] status 】命令可以提供服務器狀態信息,也可以在操作系統上使用 mysqladmin extended-status 命令獲得這些消息。

show [session|global] status 可以根據需要加上參數“session”或者“global”來顯示 session 級(當前連接)的統計結果和 global 級(自數據庫上次啓動至今)的統計結果。如果不寫,默認使用參數是“session”。

(1)Com_xxx 表示每個 xxx 語句執行的次數,我們通常比較關心的是以下幾個統計參數。

參數 說明
Com_select 執行 select 操作的次數,一次查詢只累加 1
Com_insert 執行 INSERT 操作的次數,對於批量插入的 INSERT 操作,只累加一次
Com_update 執行 UPDATE 操作的次數
Com_delete 執行 DELETE 操作的次數

在這裏插入圖片描述
上面這些參數對於所有存儲引擎的表操作都會進行累計。

(2)下面這幾個參數只是針對InnoDB 存儲引擎的,累加的算法也略有不同。

參數 說明
Innodb_rows_read select 查詢返回的行數
Innodb_rows_inserted 執行 INSERT 操作插入的行數
Innodb_rows_updated 執行 UPDATE 操作更新的行數
Innodb_rows_deleted 執行 DELETE 操作刪除的行數

在這裏插入圖片描述
通過以上幾個參數,可以很容易地瞭解當前數據庫的應用是以插入更新爲主還是以查詢操作爲主,以及各種類型的 SQL 大致的執行比例是多少。對於更新操作的計數,是對執行次數的計數,不論提交還是回滾都會進行累加。

(3)對於事務型的應用,通過 Com_commit 和 Com_rollback 可以瞭解事務提交和回滾的情況,對於回滾操作非常頻繁的數據庫,可能意味着應用編寫存在問題。

參數 說明
Com_commit 事務提交的情況
Com_rollback 事務回滾的情況

(4)此外,以下幾個參數便於用戶瞭解數據庫的基本情況。

參數 說明
Connections 試圖連接 MySQL 服務器的次數
Uptime 服務器工作時間
Slow_queries 慢查詢的次數

2、第二,定位執行效率較低的 SQL 語句

可以通過以下兩種方式定位執行效率較低的 SQL 語句。

  • 事後:通過慢查詢日誌定位那些執行效率較低的 SQL 語句,用–log-slow-queries[=file_name]選項啓動時,mysqld 寫一個包含所有執行時間超過 long_query_time 秒的 SQL 語句的日誌文件。
  • 實時:慢查詢日誌在查詢結束以後才紀錄,所以在應用反映執行效率出現問題的時候查詢慢查詢日誌並不能定位問題,可以使用 show processlist 命令查看當前 MySQL 在進行的線程,包括線程的狀態、是否鎖表等,可以實時地查看 SQL 的執行情況,同時對一些鎖表操作進行優化。
    在這裏插入圖片描述

3、第三,通過EXPLAIN分析低效SQL的執行計劃

通過以上步驟查詢到效率低的 SQL 語句後,可以通過 EXPLAIN 或者 DESC 命令獲取 MySQL如何執行 SELECT 語句的信息,包括在 SELECT 語句執行過程中表如何連接和連接的順序。

例子:

(1)先查看user_account表當前的索引

SHOW INDEX FROM user_account;

在這裏插入圖片描述

(2)看一下執行計劃,發現用了全表掃描,掃描行的數量爲很大的9876588,而且進行了回表查詢。

explain select username from user_account where username = '1';

在這裏插入圖片描述

4、確定問題,並採取相應的優化措施

經過以上步驟,基本就可以確認問題出現的原因。此時可以根據情況採取相應的措施,進行優化提高執行的效率。

在上面的例子中,已經可以確認是對user_account 表的全表掃描導致效率的不理想,那麼對該表的username字段創建索引:

(1)爲username列建立索引

-- 時間: 62.007s
ALTER TABLE user_account ADD INDEX index_user_account_username (username);

(2)再查看user_account表當前的索引。可以看到,username列建立了一個普通索引。

SHOW INDEX FROM user_account;

在這裏插入圖片描述

(3)再看一下執行計劃,發現用了普通索引掃描,掃描行的數量爲1,而且索引覆蓋,不會回表查詢。

explain select username from user_account where username = '1';

在這裏插入圖片描述

在建立索引後,對 user_account 表需要掃描的行數明顯減少,從 9876588行減少到 1 行。

由此可見,索引可以大大提高數據庫的訪問速度,尤其在大表中,這種優勢會更加明顯。

(三)定期分析、檢查、優化表

1、分析表

分析表的語法如下:

ANALYZE [ LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [,tbl_name] ...

本語句用於分析和存儲表的關鍵字分佈,分析的結果將可以使得系統得到準確的統計信息,使得 SQL 能夠生成正確的執行計劃。如果用戶感覺實際執行計劃並不是預期的執行計劃,執行一次分析表可能會解決問題。在分析期間,使用一個讀取鎖定對錶進行鎖定。這對於 MyISAM, BDB 和 InnoDB 表有作用。對於 MyISAM 表,本語句與使用 myisamchk -a 相當,下例中對錶 user_account做了表分析:

-- 時間: 0.363s
analyze table user_account;

在這裏插入圖片描述

2、檢查表

檢查表的語法如下:

CHECK TABLE tbl_name [, tbl_name] ... [option] ... option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}

檢查表的作用是檢查一個或多個表是否有錯誤。 CHECK TABLE 對 MyISAM 和 InnoDB 表有作用。對於 MyISAM 表,關鍵字統計數據被更新,例如:

-- 時間: 55.685s
check table user_account;

在這裏插入圖片描述

CHECK TABLE 也可以檢查視圖是否有錯誤,比如在視圖定義中被引用的表已不存在,舉例如下。

(1)首先創建一個測試表。創建一個和user_account同構的表。

create table user_account_temp as select * from user_account where id = -1 ;

(2)接着創建一個視圖。

-- 時間: 0.119s
create view user_account_temp_view as select * from user_account_temp;

(3)然後 CHECK 一下該視圖,發現沒有問題。
在這裏插入圖片描述

(4)現在刪除掉視圖依賴的表。

-- 時間: 0.167s
drop table user_account_temp;

(5)再來 CHECK 一下剛纔的視圖,發現報錯了。
在這裏插入圖片描述

3、優化表

優化表的語法如下:

OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

如果已經刪除了表的一大部分,或者如果已經對含有可變長度行的表(含有 VARCHAR、BLOB 或 TEXT 列的表)進行了很多更改,則應使用 OPTIMIZE TABLE 命令來進行表優化。這個命令可以將表中的空間碎片進行合併,並且可以消除由於刪除或者更新造成的空間浪費,但OPTIMIZE TABLE 命令只對 MyISAM、BDB 和 InnoDB 表起作用。

-- 時間: 91.809s
optimize table user_account;

在這裏插入圖片描述

4、注意

ANALYZE、CHECK、OPTIMIZE 執行期間將對錶進行鎖定,因此一定注意要在數據庫不繁忙的時候執行相關的操作。

5、界面操作

如果使用Navicat數據庫管理工具,可以在界面上進行以上幾個優化操作。
在這裏插入圖片描述

(四)常用 SQL 的優化

1、大批量插入數據

(1)準備工作

第一步,查看secure-file-priv設置。

一般默認情況時NULL。如果時NULL,則說明限制mysqld 不允許導入導出,需要配置。
在這裏插入圖片描述

第二步,打開MySQL的安裝根目錄,找到my.ini文件。
在這裏插入圖片描述

配置:secure_file_priv=""
在這裏插入圖片描述
secure-file-priv的值有三種情況:

參數 說明
secure_file_prive=null 限制mysqld 不允許導入導出
secure_file_priv=/path/ 限制mysqld的導入導出只能發生在默認的/path/目錄下
secure_file_priv="" 不對mysqld 的導入 導出做限制

第三步,重啓MySQL。

第四步,先新建一張測試表。

-- 時間: 0.102s
drop table user_account_temp ;
-- 時間: 0.115s
create table user_account_temp as select * from user_account where id = -1 ;

第五步,準備需要導入的數據文件。

-- 按照主鍵的順序排列。時間: 3.811s   時間: 0.862s   時間: 0.853s
select * from user_account limit 1000000 into outfile 'C:/Users/DaveDing/Desktop/a.txt';

-- 沒有按照主鍵的順序排列。時間: 1120.537s
select * from user_account order by rand() limit 1000000 into outfile 'C:/Users/DaveDing/Desktop/b.txt';

PS:導出數據的兩種方式

方式一:SQL命令

-- 按照主鍵的順序排列。時間: 3.811s   時間: 0.862s   時間: 0.853s
select * from user_account limit 1000000 into outfile 'C:/Users/DaveDing/Desktop/a.txt';

-- 沒有按照主鍵的順序排列。時間: 1120.537s
select * from user_account order by rand() limit 1000000 into outfile 'C:/Users/DaveDing/Desktop/b.txt';

方式二:如果使用Navicat數據庫管理工具,可以在界面上進行以上幾個優化操作。

圖1:開始
在這裏插入圖片描述

圖2:
在這裏插入圖片描述

圖3:
在這裏插入圖片描述

圖4:
在這裏插入圖片描述

圖5:
在這裏插入圖片描述

圖6:
在這裏插入圖片描述

圖7:完成
在這裏插入圖片描述

(2)MyISAM的表

當用 load 命令導入數據的時候,適當的設置可以提高導入的速度。

對於 MyISAM 存儲引擎的表,可以通過以下方式快速的導入大量的數據。

alter table user_account_temp disable keys;
load data infile 'C:/Users/DaveDing/Desktop/a.txt' into table user_account_temp;
alter table user_account_temp enable keys;

DISABLE KEYS 和 ENABLE KEYS 用來打開或者關閉 MyISAM 表非唯一索引的更新。在導入大量的數據到一個非空的 MyISAM 表時,通過設置這兩個命令,可以提高導入的效率。

對於導入大量數據到一個空的 MyISAM 表,默認就是先導入數據然後才創建索引的,所以不用進行設置。

上面是對MyISAM表進行數據導入時的優化措施。

(3)InnoDB的表

對於InnoDB類型的表,這種方式並不能提高導入數據的效率,可以有以下幾種方式提高InnoDB表的導入效率。

(1)因爲 InnoDB 類型的表是按照主鍵的順序保存的,所以將導入的數據按照主鍵的順序排列,可以有效地提高導入數據的效率。

按照主鍵的順序排列

-- Affected rows: 1000000    時間: 6.158s   時間: 5.923s   時間: 5.77s
load data infile 'C:/Users/DaveDing/Desktop/a.txt' into table user_account_temp;

沒有按照主鍵的順序排列

-- Affected rows: 1000000    時間: 10.57s   時間: 6.061s   時間: 5.7s
load data infile 'C:/Users/DaveDing/Desktop/b.txt' into table user_account_temp;

(2)在導入數據前執行 SET UNIQUE_CHECKS=0,關閉唯一性校驗,在導入結束後執行SET UNIQUE_CHECKS=1,恢復唯一性校驗,可以提高導入的效率。

SET UNIQUE_CHECKS=0;
-- Affected rows: 1000000 時間: 5.586s
load data infile 'C:/Users/DaveDing/Desktop/a.txt' into table user_account_temp;
SET UNIQUE_CHECKS=1;

(3)如果應用使用自動提交的方式,建議在導入前執行 SET AUTOCOMMIT=0,關閉自動提交,導入結束後再執行 SET AUTOCOMMIT=1,打開自動提交,也可以提高導入的效率。

SET AUTOCOMMIT=0;
-- Affected rows: 1000000 時間: 5.211s
load data infile 'C:/Users/DaveDing/Desktop/a.txt' into table user_account_temp;
SET AUTOCOMMIT=1;

2、優化INSERT語句

(1)準備工作

導出user_account_temp的數據,導出爲sql文件。
在這裏插入圖片描述

複製一份,構造兩份測試文件

user_account_temp.sql:單個值的INSERT 語句,數據量1000000。
在這裏插入圖片描述

user_account_temp_2.sql:多個值的 INSERT 語句,數據量1000000。
在這裏插入圖片描述

當進行數據 INSERT 的時候,可以考慮採用以下幾種優化方式。

(2)從同一客戶端插入很多行

如果同時從同一客戶端插入很多行,儘量使用多個值表的 INSERT 語句,這種方式將大大縮減客戶端與數據庫之間的連接、關閉等消耗,使得效率比分開執行的單個 INSERT 語句快(在一些情況中幾倍)。下面是一次插入多值的一個例子:

單個值的INSERT 語句:

INSERT INTO `user_account_temp`(`id`, `username`, `password`, `salt`, `sort`, `state`, `remark`) VALUES (285477, '285477', '285477', 285477, 285477, 1, NULL);
INSERT INTO `user_account_temp`(`id`, `username`, `password`, `salt`, `sort`, `state`, `remark`) VALUES (3989935, '3989935', '3989935', 3989935, 3989935, 0, '備註');
INSERT INTO `user_account_temp`(`id`, `username`, `password`, `salt`, `sort`, `state`, `remark`) VALUES (7144906, '7144906', '7144906', 7144906, 7144906, 1, '備註');
...

先清空表:

-- 時間: 0.102s
drop table user_account_temp ;
-- 時間: 0.115s
create table user_account_temp as select * from user_account where id = -1 ;

在這裏插入圖片描述

在這裏插入圖片描述

多個值的 INSERT 語句:

INSERT INTO `user_account_temp`(`id`, `username`, `password`, `salt`, `sort`, `state`, `remark`) VALUES  (285477, '285477', '285477', 285477, 285477, 1, NULL),
(3989935, '3989935', '3989935', 3989935, 3989935, 0, '備註'),
(7144906, '7144906', '7144906', 7144906, 7144906, 1, '備註'),
(5705710, '5705710', '5705710', 5705710, 5705710, 0, '備註'),
(6355515, '6355515', '6355515', 6355515, 6355515, 0, '備註'),
(7358715, '7358715', '7358715', 7358715, 7358715, 0, '備註'),
(3886991, '3886991', '3886991', 3886991, 3886991, 1, '備註')
...

先清空表:

-- 時間: 0.102s
drop table user_account_temp ;
-- 時間: 0.115s
create table user_account_temp as select * from user_account where id = -1 ;

在這裏插入圖片描述

只需1分多鐘,比前一種快了一倍。

(3)從不同客戶端插入很多行

如果從不同客戶端插入很多行,能通過使用 INSERT DELAYED 語句得到更高的速度。DELAYED 的含義是讓 INSERT 語句馬上執行,其實數據都被放在內存的隊列中,並沒有真正寫入磁盤,這比每條語句分別插入要快的多;LOW_PRIORITY 剛好相反,在所有其他用戶對錶的讀寫完後才進行插入;

INSERT delayed INTO `user_account_temp`(`id`, `username`, `password`, `salt`, `sort`, `state`, `remark`) VALUES  (285477, '285477', '285477', 285477, 285477, 1, NULL),
(3989935, '3989935', '3989935', 3989935, 3989935, 0, '備註'),
(7144906, '7144906', '7144906', 7144906, 7144906, 1, '備註'),
(5705710, '5705710', '5705710', 5705710, 5705710, 0, '備註'),
(6355515, '6355515', '6355515', 6355515, 6355515, 0, '備註'),
(7358715, '7358715', '7358715', 7358715, 7358715, 0, '備註'),
(3886991, '3886991', '3886991', 3886991, 3886991, 1, '備註')
...
(4)其它
  • 將索引文件和數據文件分在不同的磁盤上存放(利用建表中的選項);
  • 如果進行批量插入,可以增加 bulk_insert_buffer_size 變量值的方法來提高速度,但是,這隻能對 MyISAM 表使用;
  • 當從一個文本文件裝載一個表時,使用 load data infile。這通常比使用很多 INSERT 語句快 20 倍。
-- Affected rows: 1000000    時間: 6.158s   時間: 5.923s   時間: 5.77s
load data infile 'C:/Users/DaveDing/Desktop/a.txt' into table user_account_temp;

3、優化GROUP BY語句

默認情況下,MySQL 對所有 GROUP BY col1,col2…的字段進行排序。這與在查詢中指定ORDER BY col1,col2…類似。因此,如果顯式包括一個包含相同的列的 ORDER BY 子句,則對 MySQL 的實際執行性能沒有什麼影響。

explain select id,username,sum(id) from user_account group by id,username ;
explain select id,username,sum(id) from user_account group by id,username order by id,username ;

如果查詢包括 GROUP BY 但用戶想要避免排序結果的消耗,則可以指定 ORDER BY NULL禁止排序。

explain select id,username,sum(id) from user_account group by id,username order by null;

4、優化ORDER BY語句

數據結構-索引-實驗8:索引優化-優化ORDER BY語句

5、優化嵌套查詢

(1)準備工作

第一,表結構:user

-- 時間: 0s
SET NAMES utf8mb4;

-- 時間: 0s
SET FOREIGN_KEY_CHECKS = 0;

-- 時間: 0.001s
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '姓名',
  `gender` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性別:F-女;M-男',
  `age` int(3) NULL DEFAULT NULL COMMENT '年齡',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '郵箱',
  `message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '個人信息',
  `a` int(11) NULL DEFAULT NULL,
  `b` int(11) NULL DEFAULT NULL,
  `c` int(11) NULL DEFAULT NULL,
  `d` int(11) NULL DEFAULT NULL,
  `state` int(1) NOT NULL DEFAULT 0 COMMENT '狀態:0有效1無效',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- 時間: 0s
SET FOREIGN_KEY_CHECKS = 1;

第二,數據

插入基礎數據

-- 時間: 0.036s
INSERT INTO `test`.`user`(`id`, `name`, `gender`, `age`, `email`, `message`, `a`, `b`, `c`, `d`,`state`) VALUES (1, '1', 'F', 13, '[email protected]', '在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。\r\n索引提供指向存儲在表的指定列中的數據值的指針,然後根據您指定的排序順序對這些指針排序。數據庫使用索引以找到特定值,然後順指針找到包含該值的行。這樣可以使對應於表的SQL語句執行得更快,可快速訪問數據庫表中的特定信息。\r\n當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。',0,0,0,0,0);

-- 時間: 0.012s
INSERT INTO `test`.`user`(`id`, `name`, `gender`, `age`, `email`, `message`, `a`, `b`, `c`, `d`,`state`) VALUES (2, '2', 'M', 24, '[email protected]', '在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。\r\n索引提供指向存儲在表的指定列中的數據值的指針,然後根據您指定的排序順序對這些指針排序。數據庫使用索引以找到特定值,然後順指針找到包含該值的行。這樣可以使對應於表的SQL語句執行得更快,可快速訪問數據庫表中的特定信息。\r\n當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。',0,0,0,0,0);

-- 時間: 0.022s
INSERT INTO `test`.`user`(`id`, `name`, `gender`, `age`, `email`, `message`, `a`, `b`, `c`, `d`,`state`) VALUES (3, '3', 'F', 5, '[email protected]', '在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。\r\n索引提供指向存儲在表的指定列中的數據值的指針,然後根據您指定的排序順序對這些指針排序。數據庫使用索引以找到特定值,然後順指針找到包含該值的行。這樣可以使對應於表的SQL語句執行得更快,可快速訪問數據庫表中的特定信息。\r\n當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。',0,0,0,0,0);

-- 時間: 0.016s
INSERT INTO `test`.`user`(`id`, `name`, `gender`, `age`, `email`, `message`, `a`, `b`, `c`, `d`,`state`) VALUES (4, '4', 'M', 67, '[email protected]', '在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。\r\n索引提供指向存儲在表的指定列中的數據值的指針,然後根據您指定的排序順序對這些指針排序。數據庫使用索引以找到特定值,然後順指針找到包含該值的行。這樣可以使對應於表的SQL語句執行得更快,可快速訪問數據庫表中的特定信息。\r\n當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。',0,0,0,0,0);

-- 時間: 0.016s
INSERT INTO `test`.`user`(`id`, `name`, `gender`, `age`, `email`, `message`, `a`, `b`, `c`, `d`,`state`) VALUES (5, '5', 'M', 37, '[email protected]', '在關係數據庫中,索引是一種單獨的、物理的對數據庫表中一列或多列的值進行排序的一種存儲結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。索引的作用相當於圖書的目錄,可以根據目錄中的頁碼快速找到所需的內容。\r\n索引提供指向存儲在表的指定列中的數據值的指針,然後根據您指定的排序順序對這些指針排序。數據庫使用索引以找到特定值,然後順指針找到包含該值的行。這樣可以使對應於表的SQL語句執行得更快,可快速訪問數據庫表中的特定信息。\r\n當表中有大量記錄時,若要對錶進行查詢,第一種搜索信息方式是全表搜索,是將所有記錄一一取出,和查詢條件進行一一對比,然後返回滿足條件的記錄,這樣做會消耗大量數據庫系統時間,並造成大量磁盤I/O操作;第二種就是在表中建立索引,然後在索引中找到符合查詢條件的索引值,最後通過保存在索引中的ROWID(相當於頁碼)快速找到表中對應的記錄。',0,0,0,0,1);

插入10萬級別數據

-- Affected rows: 5      時間: 0.032s
-- Affected rows: 10     時間: 0.026s
-- Affected rows: 20     時間: 0.072s
-- Affected rows: 40     時間: 0.035s
-- Affected rows: 80     時間: 0.007s
-- Affected rows: 160    時間: 0.012s
-- Affected rows: 320    時間: 0.11s
-- Affected rows: 640    時間: 0.037s
-- Affected rows: 1280   時間: 0.044s
-- Affected rows: 2560   時間: 0.083s
-- Affected rows: 5120   時間: 0.223s
-- Affected rows: 10240  時間: 0.252s
-- Affected rows: 20480  時間: 0.603s
-- Affected rows: 40960  時間: 0.93s
-- Affected rows: 81920  時間: 2.728s
-- 第一步:執行15次,數據量大概爲163840 ,爲十萬級別
INSERT into user SELECT null,t.`name`,t.gender,t.age,t.email,t.message,t.a,t.b,t.c,t.d,t.state FROM `user` t; 

-- 第二步:更新a、b、c和d
-- > Affected rows: 163840 時間: 11.979s
update user set `name` = id,`a` = id,`b` = id,`c`=id,`d`=id ;

第三,檢查

表結構

desc user ;

在這裏插入圖片描述

索引

show index from user;

在這裏插入圖片描述
表信息

show table status from test where name='user';

在這裏插入圖片描述

表數據

-- 查看前100條記錄 時間: 0.001s
SELECT * FROM user t limit 100;

-- 數量:163840    時間: 2.011s
SELECT count(*) FROM user t ;
(2)嵌套查詢

有些情況下,子查詢可以被更有效率的連接(JOIN)替代。

MySQL 4.1 開始支持 SQL 的子查詢。這個技術可以使用 SELECT 語句來創建一個單列的查詢結果,然後把這個結果作爲過濾條件用在另一個查詢中。

使用子查詢可以一次性地完成很多邏輯上需要多個步驟才能完成的 SQL 操作,同時也可以避免事務或者表鎖死,並且寫起來也很容易。

嵌套查詢(子查詢)可以使用SELECT語句來創建一個單列的查詢結果,然後把這個結果作爲過濾條件用在另一個查詢中。嵌套查詢寫起來簡單,也容易理解。

但是,有時候可以被更有效率的連接(JOIN)替代。連接(JOIN)之所以更有效率一些,是因爲 MySQL 不需要在內存中創建臨時表來完成這個邏輯上的需要兩個步驟的查詢工作。

現在假如要查詢在user_account表中但是不在user表中的記錄。

嵌套查詢:

explain select * from user_account where id not in (select id from user);

在這裏插入圖片描述

連接改寫:

explain select * from user_account a left join user b on a.id = b.id where b.id is null;

在這裏插入圖片描述
連接查詢效率更高的原因,是因爲MySQL不需要在內存中創建臨時表來完成這個邏輯上需要兩個步驟的查詢工作;並且Not exists表示MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準的行, 就不再搜索了。

對於嵌套查詢的優化,歸根結底就是遵循SQL優化原則之一——減少回表查詢的I/O次數。

6、優化OR條件

對於含有 OR 的查詢子句,如果要利用索引,則 OR 之間的每個條件列都必須用到索引;如果沒有索引,則應該考慮增加索引。

重點:MySQL 在處理含有 OR字句的查詢時,實際是對 OR 的各個字段分別查詢後的結果進行了 UNION。

第一,查看索引。可以看到此時username是沒有索引的。

show index from user_account;

在這裏插入圖片描述

第二,執行計劃看一下,全表掃描。

explain select * from user_account where id = 1 or username = '1';

在這裏插入圖片描述

第三,建立索引

-- 時間: 66.734s
ALTER TABLE user_account ADD INDEX index_user_account_username (username);

第四,查看索引。可以看到此時username有索引。
在這裏插入圖片描述

第五,再次執行計劃看一下,使用了索引。

explain select * from user_account where id = 1 or username = '1';

在這裏插入圖片描述

7、使用SQL提示

SQL 提示(SQL HINT)是優化數據庫的一個重要手段,簡單來說就是在 SQL 語句中加入一些人爲的提示來達到優化操作的目的。

(1)索引準備

第一,素銀新建一個和username列有關的組合索引。

-- 時間: 74.613s
ALTER TABLE user_account ADD INDEX index_user_account_username_password (username,password);

第二,查看索引

show index from user_account;

在這裏插入圖片描述

(2)USE INDEX

在查詢語句中表名的後面,添加 USE INDEX 來提供希望 MySQL 去參考的索引列表,就可以讓 MySQL 不再考慮其他可用的索引。

explain select * from user_account where username = '1';

在這裏插入圖片描述

explain select * from user_account use index (index_user_account_username) where username = '1';

在這裏插入圖片描述

(3)IGNORE INDEX

如果用戶只是單純地想讓 MySQL 忽略一個或者多個索引,則可以使用 IGNORE INDEX。

explain select * from user_account ignore index (index_user_account_username) where username = '1';

在這裏插入圖片描述

(4)FORCE INDEX

爲強制 MySQL 使用一個特定的索引,可在查詢中使用 FORCE INDEX 作爲 HINT。

例如,當不強制使用索引的時候,因爲 id 的值都是大於 0 的,因此 MySQL 會默認進行全表掃描,而不使用索引,如下所示:

explain select * from user_account where username > '1';

在這裏插入圖片描述

但是,當使用 FORCE INDEX 進行提示時,即便使用索引的效率不是最高,MySQL 還是選擇使用了索引,這是 MySQL 留給用戶的一個自行選擇執行計劃的權力。加入 FORCE INDEX 提示後再次執行上面的 SQL:

explain select * from user_account force index (index_user_account_username) where username > '1';

在這裏插入圖片描述

執行計劃中使用了 FORCE INDEX 後的索引。

8、優化分頁查詢

在MySQL中做分頁查詢,MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下。例如“limit 100000,20”,此時MySQL排序出前1000020條數據後僅僅需要第100001到1000020條記錄,前100000條數據都會被拋棄,查詢和排序的代價非常高。由此可見MySQL的分頁處理並不是十分完美,需要我們在分頁SQL上做一些優化,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫

控制返回的總頁數並不是那麼靠譜,畢竟每頁的數據量也不能過大,數據多起來之後,控制返回的總頁數就變的不現實了。所以還是要對超過特定閾值的頁數進行 SQL 改寫

現在假設要對user_account表排序後取某一頁數據

explain select * from user_account order by username limit 100000,20;

在這裏插入圖片描述

可以看到優化器實際上做了全表掃描,處理效率不高。

優化思路:在索引上完成排序分頁的操作,最後根據主鍵關聯回表查詢所需要的其他列內容。

此處涉及到了SQL優化的兩個重要概念,索引覆蓋和回表,在前面的文章中詳細介紹過這兩個概念。通過索引覆蓋在索引上完成掃描和排序(索引有序),最後通過主鍵(InnoDB引擎索引會通過主鍵回表)回表查詢,最大限度減少回表查詢的I/O次數。
在這裏插入圖片描述

對於分頁查詢的優化,歸根結底就是遵循SQL優化原則之一——減少回表查詢的I/O次數。

五、實驗總結(包括心得體會、問題回答及實驗改進意見)

1、通過本次實驗,學會了SQL優化的一般步驟

2、瞭解了定期分析、檢查、優化表。

3、掌握了常用 SQL 的優化。

六、參考

SQL 優化——一般步驟、索引問題、優化方法(ANALYZE、CHECK、OPTIMIZE)、常用 SQL 的優化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章