Mysql的部分sql優化

sql優化

一、大批量插入數據

首先創建一張表:

CREATE TABLE `tb_user_1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(45) NOT NULL,
  `password` varchar(96) NOT NULL,
  `name` varchar(45) NOT NULL,
  `birthday` datetime DEFAULT NULL,
  `sex` char(1) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  `phone` varchar(45) DEFAULT NULL,
  `qq` varchar(32) DEFAULT NULL,
  `status` varchar(32) NOT NULL COMMENT '用戶狀態',
  `create_time` datetime NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

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

對於 InnoDB 類型的表,有以下幾種方式可以提高導入的效率:

1. 主鍵順序插入

因爲InnoDB類型的表是按照主鍵的順序保存的,所以將導入的數據按照主鍵的順序排列,可以有效的提高導入數據的效率。如果InnoDB表沒有主鍵,那麼系統會自動默認創建一個內部列作爲主鍵,所以如果可以給表創建一個主鍵,將可以利用這點,來提高導入數據的效率。

插入ID順序排列數據:

load data local infile '/www/jiaoben/sql1.log' into table `tb_user_1` fields terminated by ',' lines terminated by '\n';

在這裏插入圖片描述
這裏插入時間是24s,數據是100w條;

插入ID無序排列數據:

load data local infile '/www/jiaoben/sql2.log' into table `tb_user_2` fields terminated by ',' lines terminated by '\n';

在這裏插入圖片描述

注意:這個可能是因爲我的電腦配置的問題,導致兩者速度相差不多,甚至後者更快。(9代i7-32G- 1.5T)

2. 關閉唯一性校驗

在導入數據前執行 SET UNIQUE_CHECKS=0,關閉唯一性校驗,在導入結束後執行SET UNIQUE_CHECKS=1,恢復唯一性校驗,可以提高導入的效率。
在這裏插入圖片描述
發現只需要12s了,提高了將近一倍的性能;

本着不相信的原則,我再次嘗試無序id的插入:
在這裏插入圖片描述
結果發現有序的快了0.6s,正常!!

3. 手動提交事務

如果應用使用自動提交的方式,建議在導入前執行 SET AUTOCOMMIT=0,關閉自動提交,導入結束後再執行 SET AUTOCOMMIT=1,打開自動提交,也可以提高導入的效率。
在這裏插入圖片描述
發現更快了,所以再次嘗試兩者一起使用:
在這裏插入圖片描述
發現只提升了一點點,但是有個奇怪的現象,刪除的時間大大減少了,寫這篇博客的時候還沒去研究,之後再研究。

二、優化insert語句

當進行數據的insert操作的時候,可以考慮採用以下幾種優化方案:

  • 如果需要同時對一張表插入很多行數據時,應該儘量使用多個值表的insert語句,這種方式將大大的縮減客戶端與數據庫之間的連接、關閉等消耗。使得效率比分開執行的單個insert語句快。

原始方法:

insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');

優化後的方法:

insert into tb_test values(1,'Tom'),(2,'Cat')(3,'Jerry');
  • 在事務中進行數據插入。
start transaction;
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
commit;
  • 數據有序插入:
    錯誤方式
insert into tb_test values(4,'Tim');
insert into tb_test values(1,'Tom');
insert into tb_test values(3,'Jerry');
insert into tb_test values(5,'Rose');
insert into tb_test values(2,'Cat');

優化操作

insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
insert into tb_test values(4,'Tim');
insert into tb_test values(5,'Rose');

三、優化order by語句

1. 創建提供測試的表

CREATE TABLE `emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `age` int(3) NOT NULL,
  `salary` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4;

insert into `emp` (`id`, `name`, `age`, `salary`) values('1','Tom','25','2300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('2','Jerry','30','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('3','Luci','25','2800');
insert into `emp` (`id`, `name`, `age`, `salary`) values('4','Jay','36','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('5','Tom2','21','2200');
insert into `emp` (`id`, `name`, `age`, `salary`) values('6','Jerry2','31','3300');
insert into `emp` (`id`, `name`, `age`, `salary`) values('7','Luci2','26','2700');
insert into `emp` (`id`, `name`, `age`, `salary`) values('8','Jay2','33','3500');
insert into `emp` (`id`, `name`, `age`, `salary`) values('9','Tom3','23','2400');
insert into `emp` (`id`, `name`, `age`, `salary`) values('10','Jerry3','32','3100');
insert into `emp` (`id`, `name`, `age`, `salary`) values('11','Luci3','26','2900');
insert into `emp` (`id`, `name`, `age`, `salary`) values('12','Jay3','37','4500');

create index idx_emp_age_salary on emp(age,salary);

2. 兩種排序方式

  1. 第一種是通過對返回數據進行排序,也就是通常說的 filesort 排序,所有不是通過索引直接返回排序結果的排序都叫 FileSort 排序。
explain select * from emp order by age desc;
explain select * from emp order by age asc;

在這裏插入圖片描述
發現兩種都是沒有使用索引;

  1. 第二種通過有序索引順序掃描直接返回有序數據,這種情況即爲 using index,不需要額外排序,操作效率高。
explain select id, age from emp order by age desc;
explain select id, age from emp order by age asc;

在這裏插入圖片描述
發現索引被使用了,這樣可以優化order by;

  1. 多字段排序
    在這裏插入圖片描述
    可以總結出,按索引順序多字段排序且是 同種排序規則是效率最快的;

瞭解了MySQL的排序方式,優化目標就清晰了:儘量減少額外的排序,通過索引直接返回有序數據。where 條件和Order by 使用相同的索引,並且Order By 的順序和索引順序相同, 並且Order by 的字段都是升序,或者都是降序。否則肯定需要額外的操作,這樣就會出現FileSort。

3. Filesort 的優化

通過創建合適的索引,能夠減少 Filesort 的出現,但是在某些情況下,條件限制不能讓Filesort消失,那就需要加快 Filesort的排序操作。對於Filesort , MySQL 有兩種排序算法:

  1. 兩次掃描算法 :MySQL4.1 之前,使用該方式排序。首先根據條件取出排序字段和行指針信息,然後在排序區 sort buffer 中排序,如果sort buffer不夠,則在臨時表 temporary table 中存儲排序結果。完成排序之後,再根據行指針回表讀取記錄,該操作可能會導致大量隨機I/O操作。

  2. 一次掃描算法:一次性取出滿足條件的所有字段,然後在排序區 sort buffer 中排序後直接輸出結果集。排序時內存開銷較大,但是排序效率比兩次掃描算法要高。

MySQL 通過比較系統變量 max_length_for_sort_data 的大小和Query語句取出的字段總大小, 來判定是否那種排序算法,如果max_length_for_sort_data更大,那麼使用第二種優化之後的算法;否則使用第一種。

可以適當提高 sort_buffer_sizemax_length_for_sort_data 系統變量,來增大排序區的大小,提高排序的效率。

四、優化group by 語句

由於GROUP BY 實際上也同樣會進行排序操作,而且與ORDER BY 相比,GROUP BY 主要只是多了排序之後的分組操作。當然,如果在分組的時候還使用了其他的一些聚合函數,那麼還需要一些聚合函數的計算。所以,在GROUP BY 的實現過程中,與 ORDER BY 一樣也可以利用到索引。

如果查詢包含 group by 但是用戶想要避免排序結果的消耗, 則可以執行order by null 禁止排序。如下 :

drop index idx_emp_age_salary on emp;
explain select age,count(*) from emp group by age;

在這裏插入圖片描述
這裏發現生成臨時表,還有文件排序;

優化後

explain select age,count(*) from emp group by age order by null;

在這裏插入圖片描述
從上面的例子可以看出,第一個SQL語句需要進行"filesort",而第二個SQL由於order by null 不需要進行 “filesort”, 而上文提過Filesort往往非常耗費時間。

create index idx_emp_age_salary on emp(age,salary);

在這裏插入圖片描述
臨時表和文件排序都解決了,使用的就是索引的方式了;

五、優化嵌套查詢

Mysql4.1版本之後,開始支持SQL的子查詢。這個技術可以使用SELECT語句來創建一個單列的查詢結果,然後把這個結果作爲過濾條件用在另一個查詢中。使用子查詢可以一次性的完成很多邏輯上需要多個步驟才能完成的SQL操作,同時也可以避免事務或者表鎖死,並且寫起來也很容易。但是,有些情況下,子查詢是可以被更高效的連接(JOIN)替代。

示例 ,查找有角色的所有的用戶信息 :

explain select * from t_user where id in (select user_id from user_role );

在這裏插入圖片描述
優化後:

explain select * from t_user u , user_role ur where u.id = ur.user_id;

在這裏插入圖片描述
這裏觀察key_len,可發現-1,執行計劃中也少了一步;
連接(Join)查詢之所以更有效率一些 ,是因爲MySQL不需要在內存中創建臨時表來完成這個邏輯上需要兩個步驟的查詢工作。

六、優化OR條件

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

explain select * from emp where id = 1 or age = 30;

在這裏插入圖片描述
建議使用 union 替換 or :

explain select * from emp where id = 1 union select * from emp where age = 30;

在這裏插入圖片描述
我們來比較下重要指標,發現主要差別是 type 和 ref 這兩項

type 顯示的是訪問類型,是較爲重要的一個指標,結果值從好到壞依次是:

system > const > eq_ref > ref > fulltext > ref_or_null  > index_merge > unique_subquery > index_subquery > range > index > ALL

UNION 語句的 type 值爲 ref,OR 語句的 type 值爲 range,可以看到這是一個很明顯的差距

UNION 語句的 ref 值爲 const,OR 語句的 type 值爲 null,const 表示是常量值引用,非常快

這兩項的差距就說明了 UNION 要優於 OR 。

七、優化分頁查詢

一般分頁查詢時,通過創建覆蓋索引能夠比較好地提高性能。一個常見又非常頭疼的問題就是 limit 2000000,10 ,此時需要MySQL排序前2000010 記錄,僅僅返回2000000 - 2000010 的記錄,其他記錄丟棄,查詢排序的代價非常大 。
在這裏插入圖片描述
可以做個對比:查詢第200w頁的10條數據,耗時1.4s
在這裏插入圖片描述
而第二頁的10條數據只需要0.01s
在這裏插入圖片描述

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

select * from test t, (select id from test order by id limit 2000000, 10) a where t.id = a.id;

在這裏插入圖片描述
優化思路二
該方案適用於主鍵自增的表,可以把Limit 查詢轉換成某個位置的查詢 。

select * from test where id > 2000000 limit 10;

在這裏插入圖片描述

八、使用SQL提示

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

  • USE INDEX
  • IGNORE INDEX
  • FORCE INDEX
explain select * from tb_seller use index(idx_seller_name) where name = '小米科技';
explain select * from tb_seller ignore index(idx_seller_name) where name = '小米科技';
explain select * from tb_seller force index(idx_seller_name) where name = '小米科技';

九、總結

基本的優化,謝謝大家閱讀!!互相學習!

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