mysql優化總結-(一)存儲層面的優化(引擎,字段,範式)
接上一篇
本篇重點介紹設計層面的優化
mysql優化,主要從哪些方面去考慮
1.存儲層
數據表的存儲引擎選取,
字段類型的選取,
逆範式
2.設計層
索引的使用,
分區/分表,
sql語句的優化,
緩存的使用
存儲過程優化
3.架構層
分佈式部署(讀寫分離)
4.sql語句層
使用高效的sql語句
2.1.1索引的使用
索引的概念
利用關鍵字,就是記錄的部分數據(某個字段,某些字段,某個字段的一部分),
建立與記錄位置的對應關係,就是索引。
索引的作用:是用於快速定位實際數據位置的一種機制。
索引的類型
4種類型:
主鍵索引,唯一索引,普通索引,全文索引
索引的使用:
1.創建索引
a.建表時:
create table 表名 (
id int primary key auto_increment,
name varchar(32) not null,
age tinyint notnull,
intro text,
unique key (name),
index (age),
fulltext index(intro),
index (name,age)
)engine myisam charset utf8mb4;
name增加唯一索引,
age 增加普通索引,
intro 增加全文索引,
name,age 增加 複合索引
b.修改表:
alter table 表名 add unique key (name),add index(age),add fulltext index(intro),add index(name,age);
2.刪除索引
a.刪除主鍵索引:alter table table_name drop primary key
主鍵索引的刪除,如果沒有auto_increment 屬性
則使用 alter table 表名 drop primary key
如果在刪除主鍵索引時,該字段中有auto_increment則先去掉該屬性再刪除。
去除主鍵的auto_inrement屬性:
alter table 表名 modify id int unsigned not null comment '主鍵'
b.刪除非主鍵索引,唯一索引,全文索引,複合索引;
語法:
alter table 表名 drop index 索引的名稱;
3.查看索引
show indexes from 表名;
show index from t表名\G
show create table 表名;
show keys from 表名;
desc 表名;
創建索引注意事項:
第一:較頻繁的作爲查詢條件字段應該創建索引
select * from emp where empno = 1
第二:唯一性太差的字段不適合單獨創建索引,即使頻繁作爲查詢條件
select * from emp where sex = '男‘
第三:更新非常頻繁的字段不適合創建索引
select * from emp where logincount = 1
第四:不會出現在WHERE子句中字段不該創建索
注意:
索引具體在sql語句執行中,是在詞法語法分析,優化器優化之後,形成執行計劃過程時去進行分析用到的索引有哪些,
然後纔是執行sql語句
2.1.2 索引的數據結構
查看索引的類型:
show keys from 表名;
1、myisam的存儲引擎索引結構:
索引的節點中存儲的是數據的物理地址(磁道和扇區)
在查找數據時,查找到索引後,根據索引節點中記錄的物理地址,查找到具體的數據內容。
2、innodb的存儲引擎的索引結構
innodb的主鍵索引文件上 直接存放該行數據,稱爲聚簇索引,
非主索引指向對主鍵的引用(非主鍵索引的節點存儲是主鍵的id)
比如要通過nam創建的索引,查詢name=’liubei’的,先根據name建立的索引,找出該條記錄的主鍵id,
再根據主鍵的id通過主鍵索引找出該條記錄。
innodb的主索引文件上 直接存放該行數據,稱爲聚簇索引,非主索引指向對主鍵的引用
myisam中, 主索引和非主索引,都指向物理行(磁盤位置).
注意:
innodb來說
1: 主鍵索引 既存儲索引值,又在節點中存儲行的數據
2: 如果沒有主鍵, 則會Unique key做主鍵
3: 如果沒有unique,則系統生成一個內部的rowid做主鍵.
4: 像innodb中,主鍵的索引結構中,既存儲了主鍵值,又存儲了行數據,這種結構稱爲”聚簇索引”
聚簇索引
優勢: 根據主鍵查詢條目比較少時,不用回行(數據就在主鍵節點上)
劣勢: 如果碰到不規則數據插入時,造成頻繁的頁分裂(索引的節點移動).
2.1.3索引的使用原則
1.列獨立
只有參與條件表達式的字段獨立在關係運算符的一側,該字段纔可能使用到索引。
“獨立的列”是指索引列不能是表達式的一部分,也不能是函數的參數。
desc select * from user where age-1=10\G
2.like查詢
在使用like(模糊匹配)的時候,在左邊沒有通配符的情況下,纔可以使用索引。
在mysql裏,以%開頭的like查詢,用不到索引。
3.複合索引
最左原則:
對於創建的多列(複合)索引,只要查詢條件使用了最左邊的列,索引一般就會被使用。
還要注意,如果左邊查詢條件是一個範圍,則後面的字段有無法使用索引;
注意:在多列索引裏面,如果有多個查詢條件,要想查詢效率比較高,比如如下建立的索引,
index(a,b,c,d) 要保證最左邊的列用到索引。
4.mysql智能選擇
如果mysql認爲,全表掃描不會慢於使用索引,則mysql會選擇放棄索引,直接使用全表掃描。
一般當取出的數據量超過表中數據的20%,優化器就不會使用索引,而是全表掃描。
5.優化group by語句
默認情況下, mysql對所有的group by col1,col2進行排序。
這與在查詢中指定order by col1,col2類型,如果查詢中包括group by 但用戶想要避免排序結果的消耗,
則可以使用order by null禁止排序。
2.1.4存儲過程
1、概念
存儲過程(procedure)
概念類似於函數,就是把一段代碼封裝起來,當要執行這一段代碼的時候,可以通過調用該存儲過程來實現。
在封 裝的語句體裏面,可以同if/else ,case,while等控制結構。
可以進行sql編程。
查看現有的存儲過程。
show procedure status
2.存儲過程的優點
存儲過程(Stored Procedure)是在大型數據庫系統中,一組爲了完成特定功能的SQL 語句集,
存儲在數據庫中,經過第一次編譯後再次調用不需要再次編譯,用戶通過指定存儲過程的名字並給出參數
(如果該存儲過程帶有參數)來執行它。
存儲過程是數據庫中的一個重要對象,任何一個設計良好的數據庫應用程序都應該用到存儲過程。
(1)存儲過程只在創造時進行編譯,以後每次執行存儲過程都不需再重新編譯,
而一般SQL語句每執行一次就編譯一次,所以使用存儲過程可提高數據庫執行速度。
(2)當對數據庫進行復雜操作時(如對多個表進行Update,Insert,Query,Delete時),
可將此複雜操作用存儲過程封裝起來與數據庫提供的事務處理結合一起使用。
(3)存儲過程可以重複使用,可減少數據庫開發人員的工作量
(4)安全性高,可設定只有某些用戶才具有對指定存儲過程的使用權
(5)可以簡化查詢,把複雜的SQL語句創建一個存儲過程。
3.創建存儲過程
語法:
create procedure 存儲過程名(參數1,num)
begin
//代碼
set num = num*num;
end
參數的類型:
in(輸入參數): 表示該形參只能接受實參的數據——這是默認值,不寫就是in;
out(輸出參數):表示該形參其實是用於將內部的數據“傳出”到外部給實參;
inout(輸入輸出參數):具有上述2個功能。
案例1
查詢一個表裏面某些語句
create procedure p6()
begin
select * from goods;
end$
call p6()
案例2:
第二個存儲過程體會參數,使用參數
比如我們取出大於某個價格的商品數據
create procedure p8(price float)
begin
select * from goods where shop_price>price;
end$
說明:
(1)存儲過程中,可有各種編程元素:變量,流程控制,函數調用;
(2)還可以有:增刪改查等各種mysql語句;
(3)其中select(或show,或desc)會作爲存儲過程執行後的“結果集”返回;
(4)形參可以設定數據的“進出方向”:
(5)存儲過程是屬於數據庫,在哪個數據庫裏面定義的,就在哪個數據庫裏面調用。
,在別的數據庫裏面調用其他數據庫裏面定義的存儲過程時,會報如下提示。
procedure php.p8 not exist
4.調用存儲過程
語法:
call 存儲過程名稱(參數)
在php裏面如何調用,
mysql_query(‘call p7(5)’);
2.2.1分區
1.分區介紹
基本概念,把一個表,從邏輯上分成多個區域,便於存儲數據。
採用分區的前提,數據量非常大。
如果數據表的記錄非常多,比如達到上億條,數據表的活性就大大降低,
數據表的運行速度就比較慢、效率低下,影響mysql數據庫的整體性能,就可以採用分區解決,
分區是mysql本身就支持的技術。
查看當前mysql軟件是否支持分區;
show variables like '%partition%';
在創建(修改)表時,可以指定表,可以被分成幾個區域。
利用表選項:partition 完成。
create table table_name(
字段信息,
索引,
)engine myisam charser utf8
partition by 分區算法(分區字段)(
分區選項
);
2.分區算法:
條件分區:list (列表) range(範圍) 取模輪詢(hash,key)
(1)list分區
list :條件值爲一個數據列表。
通過預定義的列表的值來對數據進行分割
例子:假如你創建一個如下的一個表,該表保存有全國20家分公司的職員記錄,
這20家分公司的編號從1到20.而這20家分公司分佈在全國4個區域,如下表所示:
職員表:emp
id name store_id(分公司的id)
12 小寶 1
14 二寶 6
北部 1,4,5,6,17,18
南部 2,7,9,10,11,13
東部 3,12,19,20
西部 8,14,15,16
insert into emp values(12,’xiaobao’,14)
insert into emp values(15,’二bao’,17)
具體創建分區的代碼;
create table p_list(
id int,
name varchar(32),
store_id int
)engine myisam charset utf8
partition by list (store_id)(
partition p_north values in (1,4,5,6,17,18),
partition p_east values in(2,7,9,10,11,13),
partition p_south values in(3,12,19,20),
partition p_west values in(8,14,15,16)
);
創建分區表後查看文件,
添加幾條數據,測試是否用到了分區:
explain partitions select * from p_list where store_id=17\G
注意:
在使用分區時,where後面的字段必須是分區字段,才能使用到分區。
如下查詢,沒有分區條件,則會到所有的分區裏面去查找,即便如此,查詢效率也要比單表查詢高。
explain partitions select * from p_list\G
(2)Range(範圍)
這種模式允許將數據劃分不同範圍。
例如可以將一個表通過月份劃分成若干個分區
create table p_range(
id int,
name varchar(32),
birthday date
)engine myisam charset utf8
partition by range (month(birthday))(
partition p_1 values less than (4),
partition p_2 values less than(7),
partition p_3 values less than(10),
partition p_4 values less than MAXVALUE
);
less than 小於;
MAXVALUE 可能的最大值
insert into p_range values(1,’xiaobao’,’2016-09-09’);
insert into p_range values(1,’xiaobao’,’2016-11-09’);
(3)Hash(哈希)
對一個表執行HASH分區時候 MySQL會對分區鍵應用一個散列函數 確定數據應當放在N個分區中的哪個分區中.
create table p_hash(
id int,
name varchar(20),
birthday date
)engine myisam charset utf8
partition by hash(month(birthday)) partitions 5;
(4)Key(鍵值)
上面Hash模式的一種延伸,這裏的Hash Key是MySQL系統產生的。
create table p_key(
id int,
name varchar(32),
birthday date
)engine myisam charset utf8
partition by key (id) partitions 5;
3、 分區管理
具體就是對已經存在的分區進行增加、減少操作。
(1)刪除分區
刪除分區:
① 在key/hash領域不會造成數據丟失(刪除分區後數據會重新整合到剩餘的分區去)
② 在range/list領域會造成數據丟失
求餘方式(key/hash):
>alter table 表名 coalesce partition 數量;
範圍方式(range/list):
>alter table 表名 drop partition 分區名稱;
1)刪除hash類型分區
刪除分區之前,數據如下
insert into p_hash values(1,’xiaobao’,’2018-09-02’)
執行刪除分區的操作:alter table p_hash coalesce partition 4
把5個分表中的4個都刪除,只剩下一個
並且,數據沒有減少:
剩餘唯一一個分區的時候,就禁止刪除了,但是可以drop掉整個數據表:
alter table p_hash coalesce partition 1;
2)刪除list類型分表(數據有對應丟失)
alter table p_list drop partition p_north;
(2)增加分區
求餘方式: key/hash
> alter table 表名 add partition partitions 數量;
範圍方式: range/list
> alter table 表名 add partition(
partition 名稱 values less than (常量)
或
partition 名稱 values in (n,n,n)
);
-
給p_hash 增加hash分表
alter table p_hash add partition partitions 6;
增加後,一共有7個分表體現:
分表增加好後,又把數據平均地分配給各個分表存儲。
4、特別注意;
create table p_range2(
id int primary key auto_increment,
name varchar(32),
birthday date
)engine myisam charset utf8
partition by range (month(birthday))(
partition p_1 values less than (4),
partition p_2 values less than(7),
partition p_3 values less than(10),
partition p_4 values less than MAXVALUE
);
注意:
如果有主鍵或唯一索引,則創建分區的字段必須是主鍵或唯一索引的一部分
primary key(id,birthday)
不等價於如下量行代碼;
primary key(id)
primary key(birthday)
create table p_range2(
id int auto_increment,
name varchar(32),
birthday date,
primary key(id,birthday)
)engine myisam charset utf8
partition by range (month(birthday))(
partition p_1 values less than (4),
partition p_2 values less than (7),
partition p_3 values less than(10),
partition p_4 values less than MAXVALUE
);
create table p_range3(
id int auto_increment,
name varchar(32),
birthday date,
unique key(id,birthday)
)engine myisam charset utf8
partition by range (month(birthday))(
partition p_1 values less than (3),
partition p_2 values less than (6),
partition p_3 values less than(9),
partition p_4 values less than MAXVALUE
);
2.2.2分表
1.分表設計
物理方式分表設計
自己手動創建多個數據表出來
php程序需要考慮分表算法:數據往哪個表寫,從哪個表讀
QQ的登錄表。假設QQ的用戶有10億,如果只有一張表,每個用戶登錄的時候數據庫都要從這10億中查找,會很慢很慢。如果將這一張表分成100份,每張表有1000萬條,就小了很多,比如qq0,qq1,qq2…qq99表。
用戶登錄的時候,可以將用戶的id%100,那麼會得到0-99的數,查詢表的時候,將表名qq跟取模的數連接起來,就構建了表名。比如123456789用戶,取模的89,那麼就到qq89表查詢,查詢的時間將會大大縮短。
用戶註冊時,如何把用戶信息存儲到多張表裏面?假如我們分4個表;
接收表單提交的數據
$username = $_POST[‘username’];
//製作一個用戶的id
$user_id = $redis->incr(‘user_id’);
$user_id%/4=(0,1,2,3)
根據取模的值,拼接表名稱
比如$user_id = 12;
$user_id%4 = 0;
$table_name = ‘user_0’
在註冊時,需要存儲一個id和username的對應關係,藉助redis也可以;
$redis->set(‘register_’.$username,$user_id);
登錄時,如何知道查詢那張表?
根據用戶名,獲取出用戶的id,根據id%4取模,取模的值拼接成表名;
2.垂直分表(比較常用)
水平分表:是把一個表的全部記錄信息分別存儲到不同的分表之中。
垂直分表:是把一個表的全部字段分別存儲到不同的表裏邊。
有的時候,一個數據表設計好了,裏邊有許多字段,但是這些字段有的是經常使用的,有的是不常用的。在進行正常數據表操作的時候,不常用的字段也會佔據一定的資源,對整體操作的性能造成一定的干擾、影響。
爲了減少資源的開銷、提升運行效率,就可以把不常用的字段給創建到一個專門的輔表中去。
同一個業務表的不同字段分別存儲到不同數據表的過程就是“垂直分表”。
例如:
會員數據表有如下字段:
會員表: user_id 登錄名 密碼 郵箱 手機號碼 身高 體重 性別 家庭地址 身份證號碼
以上表,常用的,不常用的分開
爲了使得常用字段運行速度更快、效率更高,把常用字段給調出來,因此數據表做以下垂直分表設計:
會員表(主)user字段:user_id 登錄名 密碼 郵箱 手機號碼
會員表(輔)user_fu字段:user_id 身高 體重 性別 家庭地址 身份證號碼
以上把會員表根據字段是否常用給分爲兩個表的過程就是垂直分表。
存儲文章
經常查詢的數據 title(標題) author(作者)
dedecms裏面的分表設計,就是垂直分表設計
各種類型數據,電影數據,音樂數據 商品數據,軟件數據
把各種類型數據的公共的字段,單獨存儲到一張表裏面,比如名稱,添加時間,封面圖等等。
把各種類型數據的獨有的字段,單獨存儲到一張表裏面的;