本文配合B站學習視頻BV1es411u7we使用效果更佳。
1. MySQL版本
主流版本:5.x版
5.0 - 5.1:早期產品的延續,升級維護
5.4 - 5.x:MySQL整合了三方公司的新存儲引擎(5.5)
安裝:rpm -ivh xxx
或tar -zxvf xxx.tar.gz
查看已有的相關文件:
rpm -qa | grep xxx
安裝過程中出現沖突時需將沖突的軟件卸載掉:
yum -y remove xxx
或rpm -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
,其中mysql
和mysql.sock
比較重要
MySQL核心目錄:
MySQL安裝目錄:
/var/lib/mysql
MySQL配置文件:
/usr/share/mysql
中的``my-huge.cnf、
my-large.cnf`等MySQL命令目錄:
/usr/bin
,包含mysqladmin
、musqldump
等命令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=utf8
,character_set_client=utf8
,collation_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...
SQL優化之索引簡介
索引(Index):是幫助MySQL高效獲取數據的數據結構(B樹(MySQL默認)和哈希索引)。
B樹中的2-3樹:3層B樹可以存放上百萬條數據
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 表名
5. SQL性能問題及優化
分析SQL的執行計划:explain
,模擬SQL優化器執行SQL語句,使開發人員清除編寫的SQL狀況。SQL優化器會干擾優化。
Explian查詢執行計划
查詢執行計划:explain + SQL語句
explain select * from tb;
- id:編號
- select_type:查詢類型
- table:表
- type:類型
- possible_keys:預測用到的索引
- key:實際使用的索引
- ken_len:實際使用索引的長度
- ref:表之間的引用關系
- rows:通過索引查詢到的數據記錄數
- Extra:額外信息
案例:
建表並插入記錄:
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) ;
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) ;
上圖結果中,執行順序變為:
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) ;
上圖結果中,執行順序變為:
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' ;
將以上多表查詢轉為子查詢形式
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')) ;
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') ;
上圖結果中,執行順序變為:
c(2)-tc(3)-t(6)
select_type:查詢類型
PRIMARY
:包含子查詢SQL中的主查詢(最外層)
SUBQUERY
:包含子查詢SQL中的子查詢(非最外層)
SIMPLE
:簡單查詢(不包含子查詢和union連接查詢)EXPLAIN SELECT * FROM teacher t;
DERIVED
:衍生查詢(使用到了臨時表)
UNION
:見下例
UNION RESULT
:告知開發者哪些表存在UNION查詢,見下例
在FROM子查詢中只有一張表:查詢教課老師編號是1或2的課程信息。
EXPLAIN SELECT cr.cname FROM (SELECT * FROM course WHERE tid IN (1, 2)) cr ;
在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 ;
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;
上圖中衍生表的type為system。
const
:僅能查到一條數據的SQL,用於Primary key或Unique索引(與索引類型有關)EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;
上圖中由於僅能查到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;
上圖中僅能查到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;
上圖中type的結果不是eq_ref,其原因在於不滿足有且僅有一個,因為在teacher表中的tid唯一索引的返回的結果有6條,而連接查詢返回的結果只有3條,所以不滿足條件。
刪除后teacher表的后三條數據再次分析執行計划:
-- 刪除后三條數據 DELETE FROM teacher WHERE tid > 3; -- 分析執行計划 EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
上圖結果中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';
上圖的結果中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
index
:查詢全部索引中的數據-- 查詢teacher表中tid列的所有數據(確保tid列已有索引) 只需掃描索引表 EXPLAIN SELECT t.tid FROM teacher t; -- type為index
all
:查詢全部表中的數據-- course表沒有索引 需要全表掃描 EXPLAIN SELECT c.cid FROM course c; -- type為all
總結:
- 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') ;
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' ;
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
結果
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
結果
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
使用復合索引查詢時,使用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
結果
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' ;
上圖結果中,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' ;
添加索引后,
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;
上圖結果中,
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;
上圖結果中,按字段
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;
小結:對於復合索引,為了避免出現
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
上圖結果中,以
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 = '';
上圖結果中,由於使用的字段
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 = '';
上圖結果中,使用了字段
a1
和a3
進行查詢,而復合索引中不包含字段a3
,因此不是覆蓋索引,所以Extra中不會出現Using index
。Eg3: 覆蓋索引會對其他屬性產生影響
-- 分析執行計划 EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = ''; EXPLAIN SELECT a1, a2 FROM test02;
如果使用覆蓋索引(
Using index
),會對possible_keys
和key
造成影響:
- 若沒有where,則索引只出現在key中;
- 如果沒有索引,則索引出現在possible_keys和key中。
Using where
:可能需要回表查詢-- 分析執行計划 EXPLAIN SELECT a1, a3 FROM test02 WHERE a3 = ''; -- 需要回表查詢
上圖結果中,字段
a3
不在索引中,因此需要回表查詢,Extra的信息為Using where
。-- 分析執行計划 EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
上圖結果中使用了覆蓋索引,所以Extra中包含了
Using index
,但同時Extra的信息中還出現了Using where
,其實此時並未發生回表查詢。Using index
和Using where
一起出現時一定不發生回表查詢。備注:
Using index condition
與ICP(index condition pushdown)
(MySQL5.6新特性)- 需要回表查詢
- https://www.cnblogs.com/thrillerz/p/4166720.html
Impossible WHERE
:where子句永遠為false-- 分析執行計划 EXPLAIN SELECT * FROM test02 WHERE a1 = 'x' AND a1 = 'y'; -- where子句永遠為false,出現Impossible where
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;
從結果可以看出,該查詢語句性能較低,需要優化。
優化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;
從結果看出,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;
上圖結果中,Extra中的信息中只有
Using index
和Using where
,是覆蓋索引,不需要回表查詢,效率提升。同時覆蓋索引對possible_keys
和key
產生了影響。優化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;
上圖結果中,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';
上圖結果中,添加索引后
t
表的type由all
提升至index
,同時t
表的Extra信息為Using index
。c
表的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';
上圖結果中,
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;
上圖結果中,通過
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;
由於MySQL服務層中SQL優化器的存在,SQL優化是一種概率層面的優化。實際中是否使用優化,需要通過explain進行推測。因此在第一次查詢結果中,理想情況下應該是使用
idx_authorId
和idx_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;
盡量使用覆蓋索引(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%';
上圖結果中由於在like后面以
%
開頭導致索引失效。如果必須要like后面以%
開頭,可以使用覆蓋索引(Using index)。盡量不要使用類型轉換(顯示、隱式),否則索引失效。
-- 分析執行計划 EXPLAIN SELECT * FROM teacher WHERE tname = 'abc'; EXPLAIN SELECT * FROM teacher WHERE tname = 123;
上圖結果中,程序底層將
123
轉換為'123'
,即進行了類型轉換,因此索引失效。盡量不要使用or,否則索引失效。
-- 分析執行計划 EXPLAIN SELECT * FROM teacher WHERE tname = '' AND tcid > 1; EXPLAIN SELECT * FROM teacher WHERE tname = '' OR tcid > 1;
上圖結果中,在使用了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%';
查看具體的慢SQL:
- 通過慢查詢日志可以查看具體的SQL語句:
cat /var/lib/mysql/localhost-slow.log
用
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
Eg2:獲取訪問次數最多的3個慢SQL
mysqldumpslow -s c -t 3 /var/lib/mysql/localhost-slow.log
Eg3:按時間排序,前十條包含left join查詢語句的SQL
mysqldumpslow -s t -t 10 -g "LEFT JOIN" /var/lib/mysql/localhost-slow.log
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;
可能會出現報錯:
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;
精確分析:SQL診斷
-- SQL診斷 SHOW PROFILE ALL FOR QUERY 2; SHOW PROFILE cpu, block io FOR QUERY 2;
全局查詢日志:記錄
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;
查看日志文件:
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%';
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結果:
會話2結果:
行鎖機制:
- 如果會話1對某條數據進行DML操作(關閉自動提交的情況下),則其他操作必須等待會話或事務結束后(commit/rollback)后才能進行操作。
- 表鎖通過
UNLOCK TABLES;
釋放鎖,行鎖通過事務解鎖(commit/rollback
)。- 行鎖一次鎖一行數據,因此操作不同行的數據互不干擾。
行鎖的注意事項:
- 如果沒有索引,則行鎖會轉為表鎖。(注意回顧索引失效的情況)
- 行鎖的一種特殊情況(間隙鎖):值在范圍內,但卻不存在。MySQL會自動給間隙加間隙鎖。實際中where子句后面加范圍查詢時,實際加鎖的范圍就是查詢的范圍(不是數據庫表中實際的值)。
行鎖小結:
- InnoDB默認采用行鎖;
- 缺點在於相比表鎖性能損耗大,優點在於並發能力強以及效率高。
- 建議高並發使用InnoDB存儲引擎,否則用MyISAM存儲引擎。
分析行鎖定:
SHOW STATUS LIKE '%innodb_row_lock%';
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線程。
主從復制的問題及解決方法
存在問題:
- 主庫宕機后,數據可能丟失;
- 從庫只有一個sql Thread,主庫寫壓力大,復制很可能延時。
解決方法:
- 半同步復制:解決數據丟失的問題
- 並行復制:解決從庫復制延遲的問題