MySQL進階之MySQL索引以及索引優化


本文配合B站學習視頻BV1es411u7we使用效果更佳。

1. MySQL版本

主流版本:5.x版

  • 5.0 - 5.1:早期產品的延續,升級維護

  • 5.4 - 5.x:MySQL整合了三方公司的新存儲引擎(5.5)

安裝:rpm -ivh xxxtar -zxvf xxx.tar.gz

查看已有的相關文件:rpm -qa | grep xxx

安裝過程中出現沖突時需將沖突的軟件卸載掉:yum -y remove xxxrpm -e --nodeps xxx

驗證:mysqladmin --version

服務:

啟動服務:service mysql start

關閉服務:service mysql stop

重啟服務:service mysql restart

服務開機自啟/關閉:chkconfig mysql on/off

登錄:mysql

設置初始密碼:/usr/bin/mysqladmin -u root password 'new-password'

授權遠程連接:

授權:grant all privileges on *.* to '用戶名' @'%' identified by '密碼';

刷新權限:flush privileges;

開啟防火牆服務:systemctl start firewalld.service

開啟3306端口:firewall-cmd --zone = public --query-port = 3306/tcp

重新加載防火牆服務:firewall-cmd --reload

數據庫存放目錄:ps -ef|grep mysql

數據存放目錄:datadir=/var/lib/mysql

pid文件目錄:pid-file=/var/lib/mysql/chiaki01.pid

進入目錄cd /var/lib/mysql,其中mysqlmysql.sock比較重要

image-20200721150802383

MySQL核心目錄:

MySQL安裝目錄:/var/lib/mysql

MySQL配置文件:/usr/share/mysql中的``my-huge.cnfmy-large.cnf`等

MySQL命令目錄:/usr/bin,包含mysqladminmusqldump等命令

MySQL啟停腳本:/etc/init.d/mysql

MySQL配置文件目錄:/etc/my.cnf,不存在就復制過來cp /usr/share/mysql/my-huge.cnf /etc/my.cnf

MySQL編碼查詢:show variables like '%char%';

統一編碼為utf8:進入配置文件my.cnf進行修改

[mysql]:default-character-set=utf8

[client]:default-character-set=utf8

[server]:character_set_server=utf8character_set_client=utf8collation_server=utf8_general_ci

注意事項:修改編碼只對修改之后的創建的數據庫生效

MySQL清屏:system clear, ctrl+L

備注:5.5以上安裝的一些命令

查看初始密碼:cat /root/.mysql_sercet

安裝完初始登錄:mysql -uroot -p並輸入密碼

登錄成功設置密碼安全策略並修改密碼(5.5以上):

  • 改變密碼等級:set global validate_password_policy=0;
  • 改變密碼最小長度:set global validate_password_length=4;
  • 修改密碼:SET PASSWORD = PASSWORD('密碼');

授權:grant all privileges on *.* to '用戶名' @'%' identified by '密碼';

刷新權限:flush privileges;

開放遠程連接(關閉防火牆服務或者開放防火牆3306端口)

  • 關閉防火牆服務:systemctl stop firewalld.service
  • 開啟防火牆服務:systemctl start firewalld.service
  • 開啟3306端口:firewall-cmd --zone = public --query-port = 3306/tcp
  • 重新加載防火牆服務:firewall-cmd --reload
  • 查看服務:firewall-cmd --list-all

CentOS7安裝MySQL5.7:https://www.cnblogs.com/Mr-Rshare/p/11799945.html

2. MySQL底層原理

邏輯分層(自頂向下)

連接層:提供與客戶端連接的服務

服務層:提供各種用戶使用的接口(select等);提供SQL優化器(MySQL Query Optimizer)

引擎層:提供了各種存儲數據的方式(InnoDB和MyISAM等)

存儲層:存儲數據

引擎區別

InnoDB:事務優先(適合高並發操作;行鎖)(5.5及以上默認引擎)

MyISAM:性能優先(不支持事務;表鎖)

引擎相關SQL語句

查詢數據庫支持的引擎:show engines

查詢當前使用的引擎:show variables like '%storage_engine%';

創建數據庫對象時指定引擎:

CREATE TABLE tb (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (5),
dept VARCHAR (5)
) ENGINE = MYISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 ;

指定數據庫對象的引擎:show table status like "tb" ;

3. SQL優化

為什么要SQL優化

性能低

執行時間長

等待時間長

SQL語句欠佳(連接查詢)

索引失效

服務器參數設置不合理(緩沖、線程)

SQL解析

編寫過程:select dinstinct...from...join...on...where...group by...having...order by...limit...

解析過程:from...on...join...where...group by...having...select dinstinct...order by...limit...

🔗:https://www.cnblogs.com/annsshadow/p/5037667.html

SQL優化之索引簡介

索引(Index):是幫助MySQL高效獲取數據的數據結構(B樹(MySQL默認)和哈希索引)。

image-20200721175759138

B樹中的2-3樹:3層B樹可以存放上百萬條數據

image-20200721180540143

B+樹:B樹的一種,是MySQL使用的索引結構,數據全部存放在葉節點中。B+樹中給查詢任意的數據次數為n次,即B+樹的高度。

索引的弊端:

  • 索引本身很大,可以存放在內存/硬盤(通常為硬盤)。
  • 索引不是所有情況均適用:少量數據;頻繁更新的字段;很少使用的字段。
  • 索引確實可以提高查詢效率,但是會降低增刪改的效率。

索引的優勢:

  • 提高查詢效率(降低IO使用率)
  • 降低CPU使用率(...order by age desc,因為B樹索引已經是一個排好序的結構)

4. 索引

索引分類

單值索引:單列,一個表可以有多個單值索引

唯一索引:在單值索引基礎上,字段的值不可重復,一般為id

復合索引:多個列構成的索引(相當於二級目錄)

索引創建

創建索引(一):create 索引類型 索引名 on 表名(字段)

單值索引:create index dept_index on tb(dept);

唯一索引:create unique index name_index on tb(name);

復合索引:create index dept_name_index on tb(dept,name);

創建索引(二):alter table 表名 add 索引類型 索引名(字段)

單值索引:alter table tb add index dept_index(dept);

唯一索引:alter table tb add unique index name_index(name);

復合索引:alter table tb add index dept_name_index(dept,name);

注意事項

如果一個字段設置為主鍵,則該字段默認就是主鍵索引,與唯一索引較類似,但存在區別。

主鍵索引不能是null,唯一索引可以為null。

刪除及查詢索引

刪除索引:drop index 索引名 on 表名

查詢索引:show index from 表名

image-20200721183831423

5. SQL性能問題及優化

分析SQL的執行計划:explain,模擬SQL優化器執行SQL語句,使開發人員清除編寫的SQL狀況。SQL優化器會干擾優化。

Explian查詢執行計划

查詢執行計划:explain + SQL語句

explain select * from tb;

image-20200721184344839

  • id:編號
  • select_type:查詢類型
  • table:表
  • type:類型
  • possible_keys:預測用到的索引
  • key:實際使用的索引
  • ken_len:實際使用索引的長度
  • ref:表之間的引用關系
  • rows:通過索引查詢到的數據記錄數
  • Extra:額外信息

案例:

建表並插入記錄:

image-20200721202659213

USE myDB;

-- 試驗中先不設置主鍵和外鍵
CREATE TABLE teacherCard (
tcid INT,
tcdesc VARCHAR (30)
) ;

CREATE TABLE teacher (
tid INT ,
tname VARCHAR (20),
tcid INT
) ;

CREATE TABLE course (
cid INT ,
cname VARCHAR (20),
tid INT
) ;

INSERT INTO teacherCard VALUES(1,'tzdesc');
INSERT INTO teacherCard VALUES(2,'twdesc');
INSERT INTO teacherCard VALUES(3,'tldesc');

INSERT INTO teacher VALUES(1,'tz',1);
INSERT INTO teacher VALUES(2,'tw',2);
INSERT INTO teacher VALUES(3,'tl',3);

INSERT INTO course VALUES(1,'java',1);
INSERT INTO course VALUES(2,'html',1);
INSERT INTO course VALUES(3,'sql',2);
INSERT INTO course VALUES(4,'web',3);

explain + SQL語句:

練習:查詢課程編號為2或教師證編號為3的老師信息

EXPLAIN
SELECT 
t.* 
FROM
teacher t,
course c,
teacherCard tc 
WHERE t.tid = c.tid 
AND t.tcid = tc.tcid 
AND (c.cid = 2 
 OR tc.tcid = 3) ;

image-20200721210113142

id:編號

  • id值相同,從上往下順序執行。

執行順序t(3)-tc(3)-c(4)(括號中表示表中的記錄數)。現向teacher標值再插入3條數據,並執行同樣的SQL語句。

INSERT INTO teacher VALUES(4,'ta',4);
INSERT INTO teacher VALUES(5,'tb',5);
INSERT INTO teacher VALUES(6,'tc',6);

EXPLAIN
SELECT 
t.* 
FROM
teacher t,
course c,
teacherCard tc 
WHERE t.tid = c.tid 
AND t.tcid = tc.tcid 
AND (c.cid = 2 
 OR tc.tcid = 3) ;

image-20200721210625831

上圖結果中,執行順序變為:tc(3)-c(4)-t(6)

表的執行順序因表中記錄數的改變而改變,其原因在於:笛卡爾積記錄數最小的表優先查詢,使中間笛卡爾積最小。

驗證:刪除course表中的兩條記錄,再次執行查看結果:

DELETE FROM course WHERE cid > 2;

EXPLAIN
SELECT 
t.* 
FROM
teacher t,
course c,
teacherCard tc 
WHERE t.tid = c.tid 
AND t.tcid = tc.tcid 
AND (c.cid = 2 
 OR tc.tcid = 3) ;

image-20200721211427058

上圖結果中,執行順序變為:c(2)-tc(3)-t(6)

  • id值不同,id值越大的越優先執行(本質:在嵌套子查詢時,先查內層,在查外層)。

練習:查詢教授SQL課程的老師的描述。

EXPLAIN
SELECT 
tc.tcdesc 
FROM
teacherCard tc,
course c,
teacher t 
WHERE t.tid = c.tid 
AND t.tcid = tc.tcid 
AND c.cname = 'SQL' ;

image-20200721212234665

將以上多表查詢轉為子查詢形式

EXPLAIN
SELECT 
tc.tcdesc 
FROM
teacherCard tc 
WHERE tcid = 
(SELECT 
 t.tcid 
FROM
 teacher t 
WHERE t.tid = 
 (SELECT 
   c.tid 
 FROM
   course c 
 WHERE c.cname = 'SQL')) ;

image-20200721212712794

id值越大的先執行,執行順序為:c(2)-t(6)-tc(3)

  • id值有相同有不同:id值越大越優先,如果id值相同,從上往下依次執行。

綜合:子查詢+多表:查詢教授SQL課程的老師的描述。

EXPLAIN
SELECT 
t.tname,
tc.tcdesc 
FROM
teacher t,
teacherCard tc 
WHERE t.tcid = tc.tcid 
AND t.tid = 
(SELECT 
 c.cid 
FROM
 course c 
WHERE c.cname = 'SQL') ;

image-20200721213615144

上圖結果中,執行順序變為:c(2)-tc(3)-t(6)

select_type:查詢類型

PRIMARY:包含子查詢SQL中的主查詢(最外層)

SUBQUERY:包含子查詢SQL中的子查詢(非最外層)

SIMPLE:簡單查詢(不包含子查詢和union連接查詢)

EXPLAIN SELECT * FROM teacher t;

image-20200721214336997

DERIVED:衍生查詢(使用到了臨時表)

UNION:見下例

UNION RESULT:告知開發者哪些表存在UNION查詢,見下例

  • 在FROM子查詢中只有一張表:查詢教課老師編號是1或2的課程信息。

    EXPLAIN 
    SELECT 
      cr.cname 
    FROM
      (SELECT 
        * 
      FROM
        course 
      WHERE tid IN (1, 2)) cr ;
    

    image-20200721215219897

  • 在FROM子查詢中,如果table1 union table2,則table1(左表)就是DERIVED,而table2就是UNION:查詢教課老師編號是1或2的課程信息。

    EXPLAIN 
    SELECT 
      cr.cname 
    FROM
      (SELECT 
        * 
      FROM
        course 
      WHERE tid = 1 
      UNION
      SELECT 
        * 
      FROM
        course 
      WHERE tid = 2) cr ;
    

    image-20200721215827369

type:索引類型、類型

常用type:

system > const > eq_ref > ref > range > index > all,性能依次降低,其中system和const只是理想情況,實際能達到最高為ref。要對type進行優化的前提是要有索引

  • system:只有一條數據的系統表或衍生表只有一條數據的主查詢。

    -- 創建test01表
    CREATE TABLE test01 (tid INT, tname VARCHAR (20));
    -- test01表中插入1條數據
    INSERT INTO test01 VALUES(1,'a');
    -- 添加主鍵索引(優化type的前提)
    ALTER TABLE test01 ADD CONSTRAINT tid_pk PRIMARY KEY(tid);
    -- 分析執行計划(衍生表只有1條數據)
    EXPLAIN SELECT * FROM (SELECT * FROM test01) t WHERE t.tid = 1;
    

    image-20200721222005921

    上圖中衍生表的type為system。

  • const:僅能查到一條數據的SQL,用於Primary key或Unique索引(與索引類型有關)

    EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;
    

    image-20200721222445664

    上圖中由於僅能查到1條數據,同時用於主鍵索引,因此type為const。

    -- 刪除主鍵索引
    ALTER TABLE test01 DROP PRIMARY KEY;
    -- 添加單值索引
    ALTER TABLE test01 ADD INDEX test01_index(tid);
    -- 再次分析執行計划
    EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;
    

    image-20200721223237415

    上圖中僅能查到1條數據,但用於一般單值索引,因此type不是const。

  • eq_ref:唯一性索引,即對於每個索引鍵的查詢,返回唯一匹配行數據(有且僅有一個),常見於唯一索引主鍵索引

    -- teacherCard表設置主鍵
    ALTER TABLE teacherCard ADD CONSTRAINT tcid_pk PRIMARY KEY(tcid);
    -- teacher表設置唯一鍵約束
    ALTER TABLE teacher ADD CONSTRAINT uk_tcid UNIQUE INDEX(tcid);
    -- 連接查詢
    SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
    -- 分析執行計划
    EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
    -- 查詢teacher表
    SELECT * FROM teacher;
    

    image-20200721224915330

    上圖中type的結果不是eq_ref,其原因在於不滿足有且僅有一個,因為在teacher表中的tid唯一索引的返回的結果有6條,而連接查詢返回的結果只有3條,所以不滿足條件。

    image-20200721225245992

    刪除后teacher表的后三條數據再次分析執行計划:

    -- 刪除后三條數據
    DELETE FROM teacher WHERE tid > 3;
    -- 分析執行計划
    EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
    

    image-20200721225652482

    上圖結果中type為eq_ref。以上SQL,用到的索引是t.tcid,即teacher表中的tcid字段。如果teacher表的數據個數和鏈接連接查詢的數據個數一直,才能滿足eq_ref級別。

  • ref:非唯一性索引:對於每隔索引鍵的索引,返回匹配的所有行。

    -- 數據准備使得teacher表中tname列中存在重復tz
    INSERT INTO teacher VALUES(4,'tz',4);
    INSERT INTO teacherCard VALUES(4,'tz2222');
    -- 創建teacher表tname列的索引
    ALTER TABLE teacher ADD INDEX tname_index(tname);
    -- 使用tname作為索引進行查詢
    SELECT * FROM teacher t WHERE t.tname = 'tz';
    -- 分析執行計划
    EXPLAIN SELECT * FROM teacher t WHERE t.tname = 'tz';
    

    image-20200721231403255

    上圖的結果中type為ref。

  • range:檢索指定范圍的行,where后是一個范圍查詢(between...and...,>,< 等),其中范圍查詢使用in時,有可能失效轉為無索引all。

    -- teacher表的tid列添加索引
    ALTER TABLE teacher ADD INDEX tid_index(tid);
    -- 分析執行計划 
    EXPLAIN SELECT t.* FROM teacher t WHERE t.tid IN (1, 2); -- 失效 變為all
    EXPLAIN SELECT t.* FROM teacher t WHERE t.tid < 3; -- range
    EXPLAIN SELECT t.* FROM teacher t WHERE t.tid > 3; -- range
    EXPLAIN SELECT t.* FROM teacher t WHERE t.tid BETWEEN 1 AND 2; -- range
    

    image-20200721232424655

  • index:查詢全部索引中的數據

    -- 查詢teacher表中tid列的所有數據(確保tid列已有索引) 只需掃描索引表
    EXPLAIN SELECT t.tid FROM teacher t; -- type為index
    

    image-20200721232928744

  • all:查詢全部中的數據

    -- course表沒有索引 需要全表掃描
    EXPLAIN SELECT c.cid FROM course c; -- type為all
    

    image-20200721233349790

總結:

  • system/const:結果只有一條數據。
  • eq_ref:結果多條,但每條數據有且僅有一條(不能為0也不能為多)。
  • ref:結果多條名單每條數據是0或多(唯一則為eq_ref)。

possible_keys:可能用的索引

key:實際用的索引

possible_keys是一種預測,不准。如果possible_keys和key是null,表示沒有使用索引。

Eg1:

-- 確保添加索引
-- 將未添加索引的字段添加索引
ALTER TABLE course ADD INDEX cname_index(cname);
-- 分析執行計划
EXPLAIN
SELECT 
t.tname,
tc.tcdesc 
FROM
teacher t,
teacherCard tc 
WHERE t.tcid = tc.tcid 
AND t.tid = 
(SELECT 
 c.cid 
FROM
 course c 
WHERE c.cname = 'SQL') ;

image-20200721234939811

Eg2:

-- 確保添加索引
-- 分析執行計划
EXPLAIN 
SELECT 
tc.tcdesc 
FROM
teacherCard tc,
course c,
teacher t 
WHERE t.tid = c.tid 
AND t.tcid = tc.tcid 
AND c.cname = 'SQL' ;

image-20200721235402174

key_len:索引的長度

作用:用於判斷復合索引是否被完全使用。

常識:

  • utf8:1個字符3個字節
  • gbk:1個字符2個字節
  • latin:1個字符1個字節

固定長度的索引類型:

Eg1-1:

-- 創建test_kl表用於key_len的試驗
CREATE TABLE test_kl (NAME CHAR(20) NOT NULL DEFAULT ''); -- name字段非空
-- 添加單值索引
ALTER TABLE test_kl ADD INDEX name_index(NAME);
-- 分析執行計划
EXPLAIN SELECT * FROM test_kl WHERE NAME = '';  -- key_len = 60

image-20200722000141211

結果key_len = 60。原因是在utf8中一個char類型字符占3個字節,所以60 = 3 * 20

Eg1-2:

-- 添加字段
ALTER TABLE test_kl ADD COLUMN name1 CHAR(20); -- name1字段可以為空
-- 添加單值索引
ALTER TABLE test_kl ADD INDEX name1_index(name1);
-- 分析執行計划
EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 61

image-20200722000808468

結果key_len = 61如果索引字段可以為null,則會使用1個字節作為標識。

Eg1-3:

-- 刪除索引
DROP INDEX name_index ON test_kl;
DROP INDEX name1_index ON test_kl;
-- 添加復合索引
ALTER TABLE test_kl ADD INDEX name_name1_index(NAME,name1);
-- 分析執行計划 使用name1字段
EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 121
-- 分析執行計划 使用name字段
EXPLAIN SELECT * FROM test_kl WHERE name = ''; -- key_len = 60

image-20200722001652172

使用復合索引查詢時,使用name字段導致復合索引沒有被完全使用,使得key_len = 60,使用name1字段使得復合索引被完全使用,key_len = 121

可變長度的索引類型:

Eg2:

-- 添加新字段
ALTER TABLE test_kl ADD COLUMN name2 VARCHAR(20); -- 可以為null
-- 添加單值索引
ALTER TABLE test_kl ADD INDEX name2_index(name2);
-- 分析執行計划
EXPLAIN SELECT * FROM test_kl WHERE name2 = ''; -- key_len = 63

image-20200722002446356

結果key_len = 63。原因是63 = 3 * 20 + 1 (標識null) + 2 (標值可變長度)

ref:表之間的引用關系

作用:指明當前表所參照的字段。注意與type中的ref值區分。

Eg:

-- 分析執行計划
EXPLAIN 
SELECT 
  * 
FROM
  course c,
  teacher t 
WHERE c.tid = t.tid 
  AND t.tname = 'tw' ;

image-20200722153817868

上圖結果中,where后的條件包含兩部分c.tid = t.tid以及t.tname = 'tw'。對於前一部分,c.tid參照的字段為t表中的t.tid,由於c.tid未設置索引,所以ref的值為null;對於后一部分,t.tname參照的字段為'tw',是一個給定的常量,所以ref的值為const

t表中的c.tid添加索引后重新分析執行計划:

-- course表的tid字段添加索引
ALTER TABLE course ADD INDEX tid_index(tid);
-- 分析執行計划
EXPLAIN 
SELECT 
  * 
FROM
  course c,
  teacher t 
WHERE c.tid = t.tid 
  AND t.tname = 'tw' ;

image-20200722154806103

添加索引后,c.tid = t.tid條件中c表的c.tid字段參照了t表的t.tid字段,所以ref的值為myDB.t.tid

rows:通過索引查詢的記錄數

Eg:

-- 分析執行計划
EXPLAIN 
SELECT 
  * 
FROM
  course c,
  teacher t 
WHERE c.tid = t.tid 
  AND t.tname = 'tz' ;
-- 查詢
SELECT * FROM course c, teacher t WHERE c.tid = t.tid AND t.tname = 'tz';
-- 查詢c表
SELECT * FROM course;
-- 查詢t表
SELECT * FROM teacher;

image-20200722160101463

上圖結果中,c表通過索引查詢得到的記錄數為2條,所以c表的rows值為2;雖然執行查詢語句得到了t表的2條記錄,但是其是重復的,真正通過t表索引查詢得到的記錄只有1條,所以t表的rows值為1。

Extra:額外信息

常見信息:

  • Using filesort:性能消耗大;需要“額外”的一次排序(查詢),常見於order by語句中。

    Eg1:單值索引

    -- 創建新表
    -- 創建新表並添加單值索引
    CREATE TABLE test02 (
      a1 CHAR(3),
      a2 CHAR(3),
      a3 CHAR(3),
      INDEX idx_a1 (a1),
      INDEX idx_a2 (a2),
      INDEX idx_a3 (a3)
    ) ;
    -- 分析執行計划
    EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a1;
    EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;
    

    image-20200722161406958

    上圖結果中,按字段a1排序時Extra的值為Using where,按字段a2排序時Extra的值為Using where; Using filesort。由於查詢的字段是a1,當按a1排序時,就按照查出來的結果排序即可,然而當按a2排序時就需要以a2為字段進行一次額外的查詢,然后將查詢的結果排序,所以Extra的信息中包含Using filesort

    小結:對於單索引,如果排序和查找是同一字段,則不會出現Using filesort的情況,反之則會出現。為了避免這一問題,可以采用如下方法:where哪些字段就order by哪些字段。

    Eg2:復合索引(滿足最左前綴原則

    -- 刪除單值索引
    DROP INDEX idx_a1 ON test02;
    DROP INDEX idx_a2 ON test02;
    DROP INDEX idx_a3 ON test02;
    -- 添加復合索引
    ALTER TABLE test02 ADD INDEX idx_a1_a2_a3(a1,a2,a3);
    -- 分析執行計划
    EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a3; -- Using filesort
    EXPLAIN SELECT * FROM test02 WHERE a2 = '' ORDER BY a3; -- Using filesort
    EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;
    

    image-20200722170329145

    小結:對於復合索引,為了避免出現Using filesort,where和order by按照復合索引的順序使用,不要跨列或無序使用。

  • Using temporary:性能損耗大,用到了臨時表,常見於group by語句中。

    Eg1:

    -- 分析執行計划
    EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a1;
    EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a2; -- Using temporary
    

    image-20200722174036751

    上圖結果中,以a1索引對字段a1進行查詢卻按字段a2進行分組,導致需要用到臨時表,Extra中出現Using temporary。要避免這種情況可以采用如下方法:查詢哪些列就根據那些列group by。

  • Using index:性能提升;覆蓋索引。原因:出現Using index,說明不讀取原文件,只從索引文件中獲取數據,即不需要回表查詢。只要是用到的列全部都在索引中,就是覆蓋索引。

    Eg1:test02表中存在復合索引(idx_a1_a2_a3);正例

    -- 分析執行計划
    EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
    

    image-20200722175753991

    上圖結果中,由於使用的字段a1和字段a2均包含在復合索引中,是覆蓋索引,因此Extra中出現Using index

    Eg2:反例

    -- 刪除復合索引
    DROP INDEX idx_a1_a2_a3 ON test02;
    -- 添加字段a1和a2的復合索引
    ALTER TABLE test02 ADD INDEX idx_a1_a2(a1, a2);
    -- 分析執行計划
    EXPLAIN SELECT a1, a3 FROM test02 WHERE a1 = '' OR a3 = '';
    

    image-20200722180433937

    上圖結果中,使用了字段a1a3進行查詢,而復合索引中不包含字段a3,因此不是覆蓋索引,所以Extra中不會出現Using index

    Eg3: 覆蓋索引會對其他屬性產生影響

    -- 分析執行計划
    EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
    EXPLAIN SELECT a1, a2 FROM test02;
    

    image-20200722180957082

    如果使用覆蓋索引(Using index),會對possible_keyskey造成影響:

    • 若沒有where,則索引只出現在key中;
    • 如果沒有索引,則索引出現在possible_keys和key中。
  • Using where:可能需要回表查詢

    -- 分析執行計划
    EXPLAIN SELECT a1, a3 FROM test02 WHERE a3 = ''; -- 需要回表查詢
    

    image-20200722181915946

    上圖結果中,字段a3不在索引中,因此需要回表查詢,Extra的信息為Using where

    -- 分析執行計划
    EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
    

    image-20200722182125366

    上圖結果中使用了覆蓋索引,所以Extra中包含了Using index,但同時Extra的信息中還出現了Using where,其實此時並未發生回表查詢。Using indexUsing where一起出現時一定不發生回表查詢。

    備注:

  • Impossible WHERE:where子句永遠為false

    -- 分析執行計划
    EXPLAIN SELECT * FROM test02 WHERE a1 = 'x' AND a1 = 'y'; -- where子句永遠為false,出現Impossible where
    

    image-20200722183328211

6. 優化案例

單表優化

准備:

-- 創建book表
CREATE TABLE book (
bid INT PRIMARY KEY,
NAME VARCHAR (20) NOT NULL,
authorId INT NOT NULL,
publicId INT NOT NULL,
typeId INT NOT NULL
) ;

-- 插入數據
INSERT INTO book VALUES(1, 'tjava', 1, 1, 2);
INSERT INTO book VALUES(2, 'tc', 2, 1, 2);
INSERT INTO book VALUES(3, 'wx', 3, 2, 1);
INSERT INTO book VALUES(4, 'math', 4, 2, 3);

查詢:typeId =2或typeId=3且authorID=1的bid

-- 查詢
SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
-- 分析執行計划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

image-20200722185446733

從結果可以看出,該查詢語句性能較低,需要優化。

優化1:添加復合索引

-- 添加索引
ALTER TABLE book ADD INDEX idx_bid_tid_aid(bid, typeId, authorId);
-- 分析執行計划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

image-20200722185859492

從結果看出,type由all提升為index,Extra的信息中出現Using index,但是Using filesort仍然存在,繼續優化。

優化2:按照SQL的實際解析順序調整索引順序,重新添加索引。

-- 刪除索引
DROP INDEX idx_bid_tid_aid ON book;
-- 按解析順序添加索引
ALTER TABLE book ADD INDEX idx_tid_aid_bid(typeId, authorId, bid);
-- 分析執行計划
EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

image-20200722190805875

上圖結果中,Extra中的信息中只有Using indexUsing where,是覆蓋索引,不需要回表查詢,效率提升。同時覆蓋索引對possible_keyskey產生了影響。

優化3:提升type級別。因為使用范圍查新時in有時會失效,因此交換索引的順序,同時改變查詢語句where子句的順序。

-- 刪除索引
DROP INDEX idx_tid_aid_bid ON book;
-- 按解析順序添加索引
ALTER TABLE book ADD INDEX idx_aid_tid_bid(authorId, typeId, bid);
-- 分析執行計划
EXPLAIN SELECT bid FROM book WHERE  authorId = 1 AND typeId IN (2, 3) ORDER BY typeId DESC;

image-20200722191637938

上圖結果中,type由index提升至ref,性能進一步提升。

小結:

  • 最左前綴原則,保持索引的定義和使用的順序一致性;
  • 索引需要逐步優化;
  • 將含in的范圍查詢放到where子句最后防止失效。

兩表優化

准備:

-- 創建表
CREATE TABLE teacher2 (tid INT PRIMARY KEY, cid INT NOT NULL) ;
CREATE TABLE course2 (cid INT, cname VARCHAR (20)) ;
-- 插入數據
INSERT INTO course2 VALUES(1,'java');
INSERT INTO course2 VALUES(2,'python');
INSERT INTO course2 VALUES(3,'kotlin');

INSERT INTO teacher2 VALUES(1,2);
INSERT INTO teacher2 VALUES(2,1);
INSERT INTO teacher2 VALUES(3,3);

Eg:左連接添加索引進行優化

小表驅動大表:where 小表.x = 大表.x

索引建立在經常使用的字段上

-- 不加索引分析執行計划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';
-- 添加索引
ALTER TABLE teacher2 ADD INDEX index_teacher2_cid(cid);
-- 添加索引分析執行計划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';

image-20200722215408580

上圖結果中,添加索引后t表的type由all提升至index,同時t表的Extra信息為Using indexc表的Extra中出現Using join buffer表明MySQL引擎使用了連接緩存。

Eg:繼續添加索引

-- 添加索引
ALTER TABLE course2 ADD INDEX index_course2_cname(cname);
-- 分析執行計划
EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';

image-20200722220104508

上圖結果中,c表和t表的type均提升至ref

三表優化

原則:

  • 小表驅動大表
  • 索引建立在經常查詢的字段上

7. 避免索引失效的原則

原則:

  • 復合索引

    • 復合索引不要跨列或無序使用(最左前綴原則);
    • 復合索引盡量使用全索引匹配。
  • 不要在索引上進行任何操作(計算、函數、類型轉換),否則索引失效。

    Eg:

    -- 查看索引
    SHOW INDEX FROM book;
    -- 分析執行計划
    EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId * 2 = 2;
    EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId * 2 = 2;
    EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId = 2;
    

    image-20200722222407730

    上圖結果中,通過key_len可以清楚地看出對索引進行操作導致索引失效。值得注意地是,復合索引中,如果左側失效,其右側全部失效(最左前綴)。

  • 復合索引中不能使用不等於(!= ,<>)或is null(is not null),否則自身以及右側索引全部失效。由於SQL優化器的原因,大多情況下,范圍查詢(>, <, in)之后的索引失效。

    Eg:

    -- 刪除添加索引
    DROP INDEX idx_aid_tid_bid ON book;
    ALTER TABLE book ADD INDEX idx_authorId(authorId);
    ALTER TABLE book ADD INDEX idx_typeId(typeId);
    -- 分析執行計划
    EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId = 2;
    EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId <> 2;
    

    image-20200722224158757

    由於MySQL服務層中SQL優化器的存在,SQL優化是一種概率層面的優化。實際中是否使用優化,需要通過explain進行推測。因此在第一次查詢結果中,理想情況下應該是使用idx_authorIdidx_typeId兩個索引,但實際中只使用了idx_authorId。第二次查詢中由於對idx_authorId使用了不等於操作,使得idx_authorId索引失效,而使用了idx_typeId索引。第三次查詢中,兩個索引都進行了不等於操作,使得索引都失效。

    SQL優化器影響的例子:

    -- 刪除添加索引
    DROP INDEX idx_authorId ON book;
    DROP INDEX idx_typeId ON book;
    ALTER TABLE book ADD INDEX idx_aid_tid(authorId, typeId);
    -- 分析執行計划
    -- 復合索引全部使用
    EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    -- 復合索引全部失效
    EXPLAIN SELECT * FROM book WHERE authorId > 1 AND typeId = 2;
    -- 復合索引全部使用
    EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId > 2;
    -- 復合索引部分失效
    EXPLAIN SELECT * FROM book WHERE authorId < 1 AND typeId = 2;
    -- 復合索引全部失效
    EXPLAIN SELECT * FROM book WHERE authorId < 4 AND typeId = 2;
    

    image-20200722225836216

  • 盡量使用覆蓋索引(Using index)。

  • like盡量以常量開頭,不要以%開頭,否則索引失效。

    -- 查看索引
    SHOW INDEX FROM teacher;
    -- 分析執行計划
    EXPLAIN SELECT * FROM teacher WHERE tname LIKE 'x%';
    EXPLAIN SELECT * FROM teacher WHERE tname LIKE '%x%';
    EXPLAIN SELECT tname FROM teacher WHERE tname LIKE '%x%';
    

    image-20200722231144962

    上圖結果中由於在like后面以%開頭導致索引失效。如果必須要like后面以%開頭,可以使用覆蓋索引(Using index)。

  • 盡量不要使用類型轉換(顯示、隱式),否則索引失效。

    -- 分析執行計划
    EXPLAIN SELECT * FROM teacher WHERE tname = 'abc';
    EXPLAIN SELECT * FROM teacher WHERE tname = 123;
    

    image-20200722231741939

    上圖結果中,程序底層將123轉換為'123',即進行了類型轉換,因此索引失效。

  • 盡量不要使用or,否則索引失效。

    -- 分析執行計划
    EXPLAIN SELECT * FROM teacher WHERE tname = '' AND tcid > 1;
    EXPLAIN SELECT * FROM teacher WHERE tname = '' OR tcid > 1;
    

    image-20200722232117474

    上圖結果中,在使用了or之后,索引失效。

8. 一些其他的優化方法

EXIST和IN

exist和in:如果主查詢的數據集大,則使用in,效率高;如果子查詢的數據集大,則使用exist,效率高。

exist語法:將主查詢的結果放到子查詢中進行條件校驗(看子查詢是否有數據,如果有數據,則校驗成功),如果校驗成功則保留查詢結果,否則不保留。

SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher); -- 有效
SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher WHERE tid = 60); -- 失效

ORDER BY

order by優化:常出現Using filesort,Using filesort有兩種排序算法:雙路排序、單路排序(根據IO的次數)。

MySQL4.1之前,默認使用雙路排序:掃描2次磁盤 - ① 從磁盤讀取排序字段並在緩沖區中進行排序;②掃描其他字段。MySQL4.1之后,為了減少IO訪問次數消耗性能,默認使用單路排序:只掃描一次磁盤 - 一次讀取全部字段並在緩沖區進行排序,但存在隱患(實際上不一定真的是一次IO,可能是多次IO)。原因在於如果數據量特別大則無法將所有數據一次性讀取完畢,因此會進行分片多次讀取。

注意:

  • 單路排序比雙路排序會占用更多的緩沖區(buffer);

  • 單路排序在使用時,如果數據量特別大,可以考慮擴增buffer的容量大小

    -- 調整buffer的容量大小 單位byte
    SET max_length_for_sort_data = 1024;
    
  • 如果需要排序的數據(order by 后的字段)總大小超過了max_length_for_sort_data定義的字節數,那么MySQL會自動由單路排序切換為雙路排序。

提高order by查詢效率的策略:

  • 選擇使用單路排序或雙路排序,調整buffer容量的大小;
  • 盡量避免select * ...語句;
  • 復合索引不要跨列使用,避免出現Using filesort
  • 盡量保證全部排序字段的排序一致性(都是升序或都是降序)。

9. SQL排查

慢查詢日志

MySQL提供的一種日志記錄,用於記錄MySQL中響應時間超過閥值的SQL語句(long_query_time:默認10秒)。

慢查詢日志默認關閉,在開發調優是建議打開,最終部署時關閉。

檢查是否開啟了慢查詢日志以及開啟慢查詢日志:

-- 檢查是否開啟
SHOW VARIABLES LIKE '%slow_query_log%';
-- 臨時開啟,重啟MySQL服務失效
SET GLOBAL slow_query_log = 1;
-- 永久開啟
-- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
-- slow_query_log = 1
-- slow_query_log_file = /vaar/lib/mysql/localhost-slow.log

查詢並修改慢查詢閥值:

-- 查詢慢查詢閥值
SHOW VARIABLES LIKE '%long_query_time%';
-- 臨時設置慢查詢閥值
-- 設置完畢后重新登錄生效
SET GLOBAL long_query_time = 3;
-- 永久開啟
-- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
-- long_query_time = 3

Eg:

-- 查詢慢查詢閥值
SHOW VARIABLES LIKE '%long_query_time%';
-- 查詢線程休眠4秒
SELECT SLEEP(4);
-- 查看響應時間超過慢查詢閥值的SQL條數
SHOW GLOBAL STATUS LIKE '%slow_queries%';

image-20200723004948431

查看具體的慢SQL:

  • 通過慢查詢日志可以查看具體的SQL語句:cat /var/lib/mysql/localhost-slow.log

image-20200723005054111

  • mysqldumpslow工具查看慢SQL,可以通過一些過濾條件找到需要定位的慢SQL

    s:排序方式;r:逆序;l:鎖定時間;g:正則表達式

    -- 多增加幾條慢SQL
    SELECT SLEEP(5);
    SELECT SLEEP(3);
    SELECT SLEEP(3);
    SELECT SLEEP(3);
    

    Eg1:獲取返回記錄最多的3個慢SQL

    mysqldumpslow -s r -t 3 /var/lib/mysql/localhost-slow.log

    image-20200723010323113

    Eg2:獲取訪問次數最多的3個慢SQL

    mysqldumpslow -s c -t 3 /var/lib/mysql/localhost-slow.log

    image-20200723010427387

    Eg3:按時間排序,前十條包含left join查詢語句的SQL

    mysqldumpslow -s t -t 10 -g "LEFT JOIN" /var/lib/mysql/localhost-slow.log

    image-20200723010722850

10. 分析海量數據

模擬海量數據

利用存儲過程(無return)/存儲函數(有return):

-- 創建新數據庫並切換
CREATE DATABASE testdata;
USE testdata;
-- 創建新表
CREATE TABLE dept (
dno INT PRIMARY KEY DEFAULT 0,
dname VARCHAR (20) NOT NULL DEFAULT '',
loc VARCHAR (20) DEFAULT ''
) ENGINE = INNODB DEFAULT CHARSET = utf8 ;

CREATE TABLE emp (
eid INT PRIMARY KEY,
ename VARCHAR (20) NOT NULL DEFAULT '',
job VARCHAR (20) NOT NULL DEFAULT '',
deptno INT NOT NULL DEFAULT 0
) ENGINE = INNODB DEFAULT CHARSET = utf8 ;
-- 通過存儲函數插入海量數據
-- 創建隨機字符串模擬員工名稱
DELIMITER $
CREATE FUNCTION randstring(n INT) RETURNS VARCHAR (255) 
BEGIN
DECLARE all_str VARCHAR (100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
DECLARE return_str VARCHAR (255) DEFAULT '' ;
DECLARE i INT DEFAULT 0 ;
WHILE
 i < n DO SET return_str = CONCAT(
   return_str,
   SUBSTRING(all_str, FLOOR(RAND() * 52) + 1, 1)
 ) ;
 SET i = i + 1 ;
END WHILE ;
RETURN return_str ;
END $

-- 創建隨機數字模擬編號
DELIMITER $
CREATE FUNCTION ran_num () RETURNS INT (5) 
BEGIN
DECLARE i INT DEFAULT 0 ;
SET i = FLOOR(RAND() * 100) ;
RETURN i ;

END $

-- 通過存儲過程向emp表插入海量數據
DELIMITER $
CREATE PROCEDURE insert_emp (
IN eid_start INT (10),
IN data_times INT (10)
) 
BEGIN
DECLARE i INT DEFAULT 0 ;
SET autocommit = 0 ;
REPEAT
 INSERT INTO emp 
 VALUES
   (
     eid_start + i,
     randstring (5),
     'other',
     ran_num ()
   ) ;
 SET i = i + 1 ;
 UNTIL i = data_times
END REPEAT ;
COMMIT ;
END $

-- 通過存儲過程向dept表插入海量數據
DELIMITER $
CREATE PROCEDURE insert_dept (
IN dno_start INT (10),
IN data_times INT (10)
) 
BEGIN
DECLARE i INT DEFAULT 0 ;
SET autocommit = 0 ;
REPEAT
 INSERT INTO dept 
 VALUES
   (
     dno_start + i,
     randstring (6),
     randstring (8)
   ) ;
 SET i = i + 1 ;
 UNTIL i = data_times 
END REPEAT ;
COMMIT ;
END $

-- 插入數據
CALL insert_emp(1000,800000);
CALL insert_dept(10,30);

-- 驗證
SELECT COUNT(1) FROM emp;
SELECT COUNT(1) FROM dept;

image-20200723105513172

可能會出現報錯:

  • SQL syntax:SQL語法有錯,需修改SQL語句

  • This function has none of DETERMINISTIC......:慢查詢日志沖突,可以按如下方式解決

    -- 臨時解決
    SHOW VARIABLES LIKE '%log_bin_trust_function_creators%';
    SET GLOBAL log_bin_trust_function_creators = 1;
    -- 永久解決
    -- 永久開啟
    -- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
    -- log_bin_trust_function_creators = 1
    

分析海量數據

  • 利用profiles:當profiling開啟后會記錄全部SQL語句的相關信息(id,執行時間和SQL語句)。缺點在於只能看多總執行時間,不能看到各個硬件消耗的時間。

    -- 查看
    SHOW VARIABLES LIKE '%profiling%';
    -- 使用
    SHOW PROFILES;
    -- 開啟
    SET profiling = ON;
    -- 查看
    SHOW VARIABLES LIKE '%profiling%';
    -- 使用
    SHOW PROFILES;
    -- 查詢
    SELECT COUNT(1) FROM dept;
    -- 使用
    SHOW PROFILES;
    

    image-20200723111837963

  • 精確分析:SQL診斷

    -- SQL診斷 
    SHOW PROFILE ALL FOR QUERY 2;
    SHOW PROFILE cpu, block io FOR QUERY 2;
    

    image-20200723112154994

  • 全局查詢日志:記錄profileing開啟后的全部SQL語句(全局的記錄操作僅僅在調優和開發過程中打開即可,在最終部署時一定關閉),在mysql.general_log表中可以查看日志。

    -- 查看
    SHOW VARIABLES LIKE '%general_log%';
    -- 將全部的SQL記錄在表中
    SET GLOBAL general_log = ON;
    SET GLOBAL log_output = 'table';
    -- 查看
    SHOW VARIABLES LIKE '%general_log%';
    -- 查詢
    SELECT * FROM emp;
    SELECT COUNT(*) FROM emp;
    -- 查看日志
    SELECT * FROM mysql.general_log;
    -- 也可以將全部的SQL記錄到文件
    SET GLOBAL general_log_file = ON;
    SET GLOBAL log_output = 'file';
    SET GLOBAL general_log_file = '/tmp/general.log';
    -- 查詢
    SELECT COUNT(1) FROM dept;
    

    image-20200723114817208

    查看日志文件:cat /tmp/general.log

11. 鎖機制

解決因資源共享而造成的並發問題。

分類:

  • 操作類型:
    • 讀鎖(共享鎖):對同一數據,多個讀操作可以同時進行,互不干擾。
    • 寫鎖(互斥鎖):如果當前寫操作沒有完成,則無法進行其他的讀操作和寫操作。
  • 操作范圍:
    • 表鎖:一次性對整張表加鎖。如MyISAM存儲引擎使用表鎖,開銷小,加鎖塊;無死鎖;但鎖的范圍大,容易發生鎖沖突,並發度低。
    • 行鎖:一次性對一條數據加鎖。如InnoDB存儲引擎使用行鎖,開銷大,加鎖慢;容易出現死鎖;鎖的范圍較小,不易發生鎖沖突,並發度高(發生高並發問題:臟讀、修改丟失、不可重復讀和幻讀)。
    • 頁鎖

表鎖(MyISAM)

加讀鎖

-- 建表設置為MyISAM引擎
CREATE TABLE tablelock (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (20)
) ENGINE MYISAM ;

-- 插入數據
INSERT INTO tablelock VALUES(NULL,'a1');
INSERT INTO tablelock VALUES(NULL,'a2');
INSERT INTO tablelock VALUES(NULL,'a3');
INSERT INTO tablelock VALUES(NULL,'a4');
INSERT INTO tablelock VALUES(NULL,'a5');

-- 查看加鎖的表
SHOW OPEN TABLES;

-- 加讀鎖
LOCK TABLE tablelock READ;

會話1(加鎖的會話):

如果會話1對表加了read鎖,那么會話1可以對該表進行讀操作,不能進行寫操作;會話1對其他表既不可以進行讀操作也不可以進行寫操作。換句話說,若會話1對數據庫中的一個表加了read鎖,那么會話1只能進行對加鎖表的讀操作。

會話2(其他會話):

其他會話能對加鎖表進行讀操作,不能進行寫操作,可以對其他表進行讀操作和寫操作。

加鎖的會話 其他會話
加鎖表的讀操作
加鎖表的寫操作 × √ 需要等待鎖釋放
其他表的讀操作 ×
其他表的寫操作 ×

加寫鎖:

-- 釋放鎖
UNLOCK TABLES;
-- 加寫鎖
LOCK TABLE tablelock WRITE;
加鎖的會話 其他會話
加鎖表的讀操作 √ 需要等待鎖釋放
加鎖表的寫操作 √ 需要等待鎖釋放
其他表的讀操作 ×
其他表的寫操作 ×

MySQL表級鎖的鎖模式:

MyISAM在執行查詢語句(SELECT)前會自動給涉及的所有表加read鎖,在執行更新操作(DML)前會自動給涉及的表加write鎖。所以對MyISAM表進行操作會出現以下情況:

  • 對MyISAM表的讀操作(加讀鎖),不會阻塞其他進程(會話)對同一表的讀請求,但會阻塞對同一表的寫請求。只有當讀鎖釋放后,才會執行其他進程的寫操作;
  • 對MyISAM表的寫操作(加寫鎖),會阻塞其他進程(會話)對同一表的讀和寫操作,只有當寫鎖釋放后,才會執行其他進程的讀寫操作。

分析表鎖定:

-- 查看加鎖的表
SHOW OPEN TABLES;

In_use:當其值為1時,表示被加了鎖。

-- 分析表鎖定的嚴重程度
SHOW STATUS LIKE 'table%';

image-20200723145716345

Table_locks_immediate:可能獲取到的鎖的數量

Table_lock_waited:需要等待的表鎖數(如果該值越大,說明存在越大的鎖競爭)。

一般建議

計算比值n = Table_locks_immediate / Table_lock_waited ,若n > 5000,建議采用InnoDB引擎,否則采用MyISAM引擎。

行鎖(InnoDB)

InnoDB存儲引擎默認使用行鎖

-- 創建表
CREATE TABLE linelock (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (20)
) ENGINE = INNODB DEFAULT CHARSET = utf8 ;

-- 插入數據
INSERT INTO linelock VALUES(NULL, '1');
INSERT INTO linelock VALUES(NULL, '2');
INSERT INTO linelock VALUES(NULL, '3');
INSERT INTO linelock VALUES(NULL, '4');
INSERT INTO linelock VALUES(NULL, '5');

兩個會話進行操作

會話1:

-- 關閉自動提交
SET autocommit = 0;
-- 會話1進行寫操作
INSERT INTO linelock VALUES(6,'a6');

會話2:

-- 關閉自動提交
SET autocommit = 0;
-- 會話2對同一條數據進行寫操作
UPDATE linelock SET NAME = 'ax' WHERE id = 6;

會話1結果:

image-20200723151946909

會話2結果:

image-20200723152104475

行鎖機制:

  • 如果會話1對某條數據進行DML操作(關閉自動提交的情況下),則其他操作必須等待會話或事務結束后(commit/rollback)后才能進行操作。
  • 表鎖通過UNLOCK TABLES;釋放鎖,行鎖通過事務解鎖(commit/rollback)。
  • 行鎖一次鎖一行數據,因此操作不同行的數據互不干擾。

行鎖的注意事項:

  • 如果沒有索引,則行鎖會轉為表鎖。(注意回顧索引失效的情況)
  • 行鎖的一種特殊情況(間隙鎖):值在范圍內,但卻不存在。MySQL會自動給間隙加間隙鎖。實際中where子句后面加范圍查詢時,實際加鎖的范圍就是查詢的范圍(不是數據庫表中實際的值)。

行鎖小結:

  • InnoDB默認采用行鎖;
  • 缺點在於相比表鎖性能損耗大,優點在於並發能力強以及效率高。
  • 建議高並發使用InnoDB存儲引擎,否則用MyISAM存儲引擎。

分析行鎖定:

SHOW STATUS LIKE '%innodb_row_lock%';

image-20200723154459708

Innodb_row_lock_current_waits:當前正在等待鎖的數量

Innodb_row_lock_time:從系統啟動到現在鎖定的總時長

Innodb_row_lock_time_avg:從系統啟動到現在鎖定的平均時長

Innodb_row_lock_time_max:從系統啟動到現在鎖定的最大時長

Innodb_row_lock_waits:從系統啟動到現在等待的次數

查詢時加鎖:

通過for update對query語句進行加鎖。

-- 開啟事務
BEGIN
-- 會話1進行查詢
SELECT * FROM linelock WHERE id = 2 FOR UPDATE; -- 加鎖

-- 會話2進行更新
UPDATE linelock SET NAME = '222' WHERE id = 2; -- 等待鎖釋放

12. 主從復制

什么是主從復制

主從復制,是用來建立一個和主數據庫完全一樣的數據庫環境,稱為從數據庫;

主數據庫一般是准實時的業務數據庫。

主從復制的作用

  • 實時災備,用於故障切換:做數據的熱備,作為后備數據庫,主數據庫服務器故障后,可切換到從數據庫繼續工作,避免數據丟失。
  • 架構擴展,提升機器性能:業務量越來越大,I/O訪問頻率過高,單機無法滿足,此時做多庫的存儲,降低磁盤I/O訪問的頻率,提高單個機器的I/O性能。
  • 讀寫分離,避免影響業務:讀寫分離使數據庫能支撐更大的並發。在報表中尤其重要。由於部分報表sql語句非常的慢,導致鎖表,影響前台服務。如果前台使用master,報表使用slave,那么報表sql將不會造成前台鎖,保證了前台速度。

主從復制的原理

  • 數據庫有個bin-log二進制文件,記錄了所有sql語句。

  • 目標就是把主數據庫的bin-log文件的sql語句復制過來。

  • 使其在從數據庫的relay-log重做日志文件中再執行一次這些sql語句即可。

  • 主從復制配置具體需要三個線程:

    • binlog輸出線程:每當有從庫連接到主庫的時候,主庫都會創建一個線程然后發送binlog內容到從庫。在從庫里,當復制開始的時候,從庫就會創建以下兩個線程進行處理。
    • 從庫I/O線程:當START SLAVE語句在從庫開始執行之后,從庫創建一個I/O線程,該線程連接到主庫並請求主庫發送binlog里面的更新記錄到從庫上。從庫I/O線程讀取主庫的binlog輸出線程發送的更新並拷貝這些更新到本地文件,其中包括relay log文件。
    • 從庫SQL線程:從庫創建一個SQL線程,這個線程讀取從庫I/O線程寫到relay log的更新事件並執行。
  • 對於每一個主從復制的連接,都有三個線程。擁有多個從庫的主庫為每一個連接到主庫的從庫創建一個binlog輸出線程,每一個從庫都有它自己的I/O線程和SQL線程。

image-20200723164752225

主從復制的問題及解決方法

存在問題:

  • 主庫宕機后,數據可能丟失;
  • 從庫只有一個sql Thread,主庫寫壓力大,復制很可能延時。

解決方法:

  • 半同步復制:解決數據丟失的問題
  • 並行復制:解決從庫復制延遲的問題

覺得有幫助的話就點個推薦吧 😃😃


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM