聊聊數據庫~4.SQL優化篇

1.5.查詢的藝術

上期回顧:http://www.javashuo.com/article/p-rkyvbstc-q.htmlhtml

本節腳本:https://github.com/lotapp/BaseCode/blob/master/database/SQL/02.索引、查詢優化.sqlmysql

文章有點小長,但認真閱讀確定會有所感觸和收穫的。PS:我把我能想到的都列下來了,若是有新的會追加,歡迎補充和糾錯~git

1.5.1.索引

大方向:減小冗餘索引,避免重複(無用)索引github

1.概念

大一統分類:算法

  1. 聚簇索引、非聚簇索引:看看數據是否與索引存儲在一塊兒(一塊兒是聚簇索引)
  2. 主鍵索引、輔助索引
  3. 稠密索引、稀疏索引
    • 是否索引了每個數據項(是則爲稠密索引)
  4. B+ Tree索引、hash索引(鍵值索引,只有Memory存儲引擎支持)、R Tree索引(空間索引,MyISAM存儲引擎支持)、Fulltext索引(全文索引)
  5. 簡單索引、組合索引

PS:索引一般作查詢條件的字段(索引是在存儲引擎級別實現的)sql

經常使用分類:數據庫

  1. 語法分類:
    1. 普通索引:一列一索引
    2. 惟一索引:設置unique以後產生(可空)
      • 能夠這麼理解:惟一+非空=主鍵
    3. 複合索引:多列一索引
  2. 物理存儲:(Innodb和MyISAM存儲引擎)
    1. 聚簇索引:通常都是主鍵
      • 數據和索引存儲在一塊兒的存儲方式
      • Innodb文件後綴:frm、ibd(數據+索引)
    2. 非聚簇索引:不是彙集索引的索引
      • 數據和索引分開存放
      • MyISAM文件後綴:frm、myd(數據)、myi(索引)
    3. PS:它倆都是b樹索引,frm(表結構)和存儲引擎無關

2. 語法基礎

  1. 查看索引:show index from tb_name;
    • show index from worktemp.userinfo\G;
    • show index from worktemp.userinfo;
  2. 建立索引:
    • create [unique] index index_name on tb_name(列名,...)
    • alter table tb_name add [unique] index [index_name] on (列名,...)
  3. 刪除索引:
    • drop index index_name on tb_name
    • alter table tb_name drop index index_name

1.5.2.執行計劃

1.往期回顧

先回顧下上節課內容:app

手寫SQL的語法順序:運維

select distinct
    <select_list>
from <tb_name>
    <join_type> join <right_table> on <join_condition>
where
    <where_condition>
group by
    <group_by_list>
having
    <having_condition>
order by
    <order_by_list>
limit <limit_number>

SQL執行順序:函數

  1. from <tb_name>
  2. on <join_condition>
  3. <join_type> join <right_table>
  4. where <where_condition>
  5. group by <group_by_list>
  6. having <having_condition>
  7. select [distinct] <select_list>
  8. order by <order_by_list>
  9. limit <limit_number>

2.基礎

語法:explain + SQL語句

執行計劃:使用explain關鍵詞能夠模擬優化器執行SQL查詢語句,通常用來分析查詢語句或者表結構的性能瓶頸

執行計劃通常用來幹這些事情:

  1. 查看錶的讀取順序
  2. 查看數據讀取操做的操做類型
  3. 查看哪些索引可使用
  4. 查看哪些索引被實際使用
  5. 查看錶之間的引用
  6. 查看每張表有多少行被優化器讀取
主要參數

主要是看這幾個參數:

  1. id:當前查詢語句中,每一個select語句的編號
    • 主要是針對子查詢、union查詢
  2. select_type:查詢類型
    • 簡單查詢:simple(通常的查詢語句)
    • 複雜查詢:(詳解見附錄1)
      • subquery:用於where中的子查詢(簡單子查詢)
      • derived:用於from中的子查詢
      • union:union語句的第一個以後的select語句
      • union result:匿名臨時表
  3. type:訪問類型(MySQL查詢表中行的方式)
    1. all:全表掃描
    2. index:根據索引的次序進行全表掃描(覆蓋索引效率更高
    3. range:根據索引作指定範圍掃描
    4. ref:返回表中全部匹配某單個值的全部行
    5. eq_ref:等同於ref,與某個值作比較且僅返回一行
    6. const:根據具備惟一性索引查找時,且返回單個行(性能最優
      • eg:主鍵、惟一鍵
    7. PS:1~6 ==> 數字越大效率越高(性能遞增),(詳解見附錄2)
  4. possible_keys:查詢可能會用到的索引
  5. key:查詢中使用了的索引
  6. key_len:索引使用的字節數(詳解見附錄3)
    • 根據這個值,能夠判斷索引使用狀況
    • eg:使用組合索引時,判斷全部索引字段是否都被查詢到
  7. ref:顯示key列索引用到了哪些列、常量值
    • 在索引列上查找數據時,用到了哪些列或者常量
  8. rows:估算大概須要掃描多少行
  9. Extra:額外信息(性能遞減)
    1. using index:使用了覆蓋索引
    2. using where:在存儲引擎檢索後,再進行一次過濾
    3. using temporary:對結果排序時會使用臨時表
    4. using filesort:對結果使用一個外部索引排序
      • 沒有有索引順序,使用了本身的排序算法
      • 可能出現的狀況:(出現這個狀況基本上都是須要優化的
        • where後面的索引列和order by|group by後面的索引列不一致(只能用到一個索引)
        • eg:explain select * from users where id<10 order by email;(只用到了id)

附錄

1.select_type

select_type:查詢類型

-- `subquery`:用於where中的子查詢(簡單子查詢)
explain
    select name, age
    from students
    where age > (select avg(age) from students);

-- `union`:union語句的第一個以後的select語句
-- `union result`:匿名臨時表
explain
    select name, age, work
    from students
    where name = '小張'
    union
    select name, age, work
    from students
    where name = '小明';

-- `derived`:用於from中的子查詢
explain
    select *
    from (select name, age, work from students where name = '小張'
          union
          select name, age, work from students where name = '小明') as tmp;

圖示輸出:
1.sql_type.png

2.type

type:訪問類型(MySQL查詢表中行的方式)

-- all:全表掃描(效率極低)
explain
    select *
    from students
    where name like '%小%';

-- index:根據索引的次序進行全表掃描(效率低)
explain
    select name, age, work
    from students
    where name like '%小%'; -- 其實就是上面全表掃描的改進版

-- range:根據索引作指定範圍掃描
explain
    select name, age, work
    from students
    where id > 5;

-- ref:返回表中全部匹配某單個值的全部行
explain
    select name, age, work
    from students
    where name = '小明';

-- eq_ref:等同於ref,與某個值作比較且僅返回一行
explain
    select *
    from userinfo
             inner join (select id from userinfo limit 10000000,10) as tmp
                        on userinfo.id = tmp.id; -- 1s

-- const:根據具備惟一性索引查找時,且返回單個行(**性能最優**)
explain
    select name, age, work
    from students
    where id = 3; -- 通常都是主鍵或者惟一鍵

圖示輸出:

2.type1.png
2.type2.png

3.key-len
  1. 是否爲空:
    • not null 不須要額外的字節
    • null 須要1字節用來標記
    • PS:索引最好不要爲null,這樣須要額外的存儲空間並且統計也變得更復雜
  2. 字符類型(char、varchar)的索引長度計算:
    • 字符編碼:(PS:不一樣字符編碼佔用的存儲空間不一樣)
      • latin1|ISO8859佔1個字節,gbk佔2個字節,utf8佔3個字節
    • 變長字段(varchar)須要額外的2個字節
      • 1字節用來保存須要的字符數
      • 1字節用來記錄長度(PS:若是列定義的長度超過255則須要2個字節【總共3字節】)
    • 定長字段(char)不須要額外的字節
  3. 數值類型、日期類型的索引長度計算:
    • 通常都是其自己長度,若是可空則+1
      • 標記是否爲空須要佔1個字節
    • PS:datetime在5.6中字段長度是5,在5.5中字段長度是8
  4. 複合索引有最左前綴的特性。若是複合索引能所有用上,則爲複合索引字段的索引長度之和
    • PS:能夠用來判斷複合索引是否所有使用到
  5. 舉個栗子:
    • eg:char(20) index 可空
      • key-len=20*3(utf8)+1(可空)=61
    • eg:varchar(20) index 可空
      • key-len=20*3(utf8)+2(可變長度)+1(是否可空的標記)=63
建表語句
create table if not exists `students`
(
    id          int unsigned auto_increment primary key,
    name        varchar(25)      not null default '' comment '姓名',
    age         tinyint unsigned not null default 0 comment '年齡',
    work        varchar(20)      not null default '普通學生' comment '職位',
    create_time datetime         not null comment '入學時間',
    datastatus  tinyint          not null default 0 comment '數據狀態'
) charset utf8 comment '學生表';

-- select current_timestamp(), now(), unix_timestamp();
insert into students(name, age, work, create_time, datastatus)
values ('111', 22, 'test', now(), 99),
       ('小張', 23, '英語課表明', now(), 1),
       ('小李', 25, '數學課表明', now(), 1),
       ('小明', 21, '普通學生', now(), 1),
       ('小潘', 27, '物理課表明', now(), 1),
       ('張小華', 22, '生物課表明', now(), 1),
       ('張小周', 22, '體育課表明', now(), 1),
       ('小羅', 22, '美術課表明', now(), 1);

-- 建立一個組合索引
create index ix_students_name_age_work on students (name, age, work);

說了這麼多題外話,如今進入正題:


1.5.3.建表優化

  1. 定長和變長分離(具體得看業務)
    • eg:varchar、text、blob等變長字段單獨出一張表和主表關聯起來便可
  2. 經常使用字段和不經常使用字段分離
    • 根據業務來分析,不經常使用的字段拎出來
  3. 在1對多須要關聯統計的字段上添加點冗餘字段
    • 分表分庫時,擴表跨庫查詢的情景(注意數據一致性)
    • eg:在分類表中添加一個數量字段,統計天天新增商品數量
      • 添加商品時,選完分類就update一下count值(次日清零)
  4. 字段類型通常都是按照這個優先級:(儘可能使用優先級高的類型)
    • 數值 > 日期 > char > varchar > text、blob
    • PS:整體原則就是夠用便可,而後儘可能避免null(不利於索引,浪費空間)
      • eg:varchar(10)和varchar(300),在錶鏈接查詢時,須要的內存是不同的
  5. 僞hash法:好比商品url是一個varchar的列
    • 這時候再建一個hash(url)以後的列,把索引設置到該列
      • 推薦使用crc32(用bigint存儲)索引空間就會小不少並且能夠避免全表掃描
      • eg:select crc32('http://www.baidu.com/shop/1.html');
    • PS:若是DBA配置了crc64,則使用;若是沒有,能夠加個條件(CRC32碰撞後的解決方案
      • 對於少部分碰撞的記錄,只須要多掃描幾行就好了,不會出現全表掃描的狀況
      • eg:select xxx from urls where crc_url=563216577 and url='url地址'

PS:須要關注的技術點:crc32

1.5.4.組合索引專題

項目裏面使用最多的是組合索引,這邊先以組合索引爲例:

1.儘量多的使用索引列,儘量使用覆蓋索引

-- 若是我查詢的時候,索引的三列都用到了,那麼速度無疑是最快的
-- Extra:using where
explain
    select id, name, age, work, create_time
    from students
    where name = '小張'
      and age = 23
      and work = '英語課表明';

-- PS:★儘可能使用覆蓋索引★(近乎萬能)
-- 覆蓋索引:僅僅查找索引就能找到所須要的數據
-- Extra:using where;using index
explain
    select name, age, work
    from students
    where name = '小張'
      and age = 23
      and work = '英語課表明';
-- PS:通常把常常select出的列設置一個組合索引,通常不超過5個

圖示:
3.1.覆蓋索引.png

2.最左前綴原則

類比火車,火車頭本身能夠開,車身要是沒有了車頭就開不了

-- 查詢的時候從最左邊的列開始,而且不跳過中間的列,一直到最後
explain
    select id, name, age, work, create_time
    from students
    where name = '小張'
      and age = 23
      and work = '英語課表明';

-- 跳過了中間的age,這時候只用到了name列的索引(work列沒用到)
explain
    select id, name, age, work, create_time
    from students
    where name = '小張'
      and work = '英語課表明';

圖示:
3.2.組合索引失效.png

再看兩個補充案例:

-- PS:若是跳過了第一列,這時候索引一個也用不到,直接全表掃描了
explain
    select id, name, age, work, create_time
    from students
    where age = 23
      and work = '英語課表明';

-- PS:列不必定須要按照指定順序來寫
explain
    select id, name, age, work, create_time
    from students
    where age = 23
      and work = '英語課表明'
      and name = '小張';

圖示:
3.2.組合索引失效2.png

2.3.範圍條件放在最後面(範圍條件後面的列索引會失效)

-- name、age、work索引生效時,key_len=140
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小張'
      and age = 23
      and work = '英語課表明';

-- 如今key_len=78 ==> work列索引就失效了(PS:age索引列未失效,只是age以後的列失效了)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小張'
      and age > 22
      and work = '英語課表明';

圖示:
3.3.範圍後面索引失效.png

補充說明:

-- 加快查詢速度可使用覆蓋索引
explain
    select name, age, work
    from students
    where name = '小張'
      and age > 22
      and work = '英語課表明';

-- PS:多個主鍵列也同樣
explain
    select id, name, age, work
    from students
    where name = '小張'
      and age > 22
      and work = '英語課表明';

-- PS:調換順序是無法解決範圍後面索引失效的(原本對順序就不在乎)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小張'
      and work = '英語課表明'
      and age > 22;

圖示:
3.3.範圍後面索引失效2.png

2.4.不在索引列上作其餘操做

容易致使全表掃描,這時候利用覆蓋索引能夠簡單優化下

1.!=is not nullis nullnot ininlike慎用

!=is not nullis null的案例

-- 1.不等於案例
-- 索引失效(key,key_len ==> null)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name != '小明'; -- <> 等同於 !=

-- 項目裏面不少使用都要使用,那怎麼辦呢?==> 使用覆蓋索引
-- key=ix_students_name_age_work,key_len=140
explain
    select name, age, work
    from students
    where name != '小明'; -- <> 等同於 !=

-- 2.is null、is not null案例
-- 索引失效(key,key_len ==> null)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name is not null;

-- 解決:覆蓋索引 key=ix_students_name_age_work,key_len=140
explain
    select name, age, work
    from students
    where name is not null;

圖示:
3.4.不等於和null.png

not inin的案例

-- 3.not in、in案例
-- 索引失效(key,key_len ==> null)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name in ('小明', '小潘', '小李');

explain
    select id, name, age, work, create_time, datastatus
    from students
    where name not in ('小明', '小潘', '小李');

-- 解決:覆蓋索引 key=ix_students_name_age_work,key_len=140
explain
    select name, age, work
    from students
    where name in ('小明', '小潘', '小李');

explain
    select name, age, work
    from students
    where name not in ('小明', '小潘', '小李');

圖示:
3.5.in和notin的案例.png

like案例:儘可能使用xxx%的方式來全文搜索,能和覆蓋索引聯合使用更好

-- 4.like案例
-- 索引不失效 key=ix_students_name_age_work,key_len=77(儘可能這麼用like)
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name like '張%';

-- 索引失效
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name like '%張';

-- 索引失效
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name like '%張%';

-- 解決:覆蓋索引 key=ix_students_name_age_work,key_len=140(儘可能避免)
explain
    select name, age, work
    from students
    where name like '%張%';

3.6.like案例.png

2.計算、函數、類型轉換(自動 or 手動)【儘可能避免】
-- 4.2.計算、函數、類型轉換(自動 or 手動)【儘可能避免】
-- 這時候索引直接失效了,並全表掃描了
-- 解決雖然可使用覆蓋索引,可是儘可能避免下面的狀況:
-- 1.計算
explain
    select id, name, age, work, create_time, datastatus
    from students
    where age = (10 + 13);

-- 2.隱式類型轉換(111==>'111')
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = 111;
-- PS:字符類型不加引號索引就直接失效了
-- 雖然覆蓋索引能夠解決,可是不要這樣作(嚴格意義上講,這個算個錯誤)

-- 3.函數
explain
    select id, name, age, work, create_time, datastatus
    from students
    where right(name, 1) = '明';

圖示:
3.7.其餘案例.png


光看沒意思,再舉個簡單的業務案例:

eg:用戶通常都是根據商品的大分類=>小分類=>品牌來查找,有時候到不看品牌,直接小分類後就本身找了。那麼組合索引能夠這麼建:index(分類id,商品價格),index(分類id,品牌id,商品價格)(通常都須要根據查詢日記來肯定)

PS:有些條例是流傳甚廣的,有些是工做中的經驗,至少都是我踩過坑的,能夠相對放心(業務不一樣優化角度不一樣)

1.5.5.寫法上的優化

5.1.or改爲union

-- 5.1.or改爲union
-- 如今高版本對只有一個or的sql語句有了優化
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小明'
       or name = '小張'
       or name = '小潘';

-- PS:等同上面or的語句
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name in ('小明', '小張', '小潘');

-- 高效
explain
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小明'
    union all
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小張'
    union all
    select id, name, age, work, create_time, datastatus
    from students
    where name = '小潘';

4.union.png

PS:union老是產生臨時表,優化起來比較棘手

通常來講union子句儘可能查詢最少的行,union子句在內存中合併結果集須要去重(浪費資源),因此使用union的時候儘可能加上all(在程序級別去重便可)

5.2.count優化

通常都是count(主鍵|索引),但如今count(*)基本上數據庫內部都優化過了(根據公司要求使用便可)

PS:記得當時踩了次坑,等復現的時候補上案例(記得好像跟null相關)

看下就知道爲何說無所謂了(PS,你count(非索引)就有所謂了)

explain
    select count(id) -- 經常使用
    from userinfo;

explain
    select count(*)
    from userinfo;

-- 你`count(非索引)`就有所謂了
explain
    select count(password)
    from userinfo;

4.2.count.png

我想說的優化是下面這個count優化案例:(有時候拆分查詢會更快)

-- 須要統計id>10000的數據總量(實際中可能會根據時間來統計)
explain
    select count(*) as count
    from userinfo
    where id > 10000; -- 2s

-- 分解成用總數-小數據統計 ==> 1s
explain
    select (select count(*) from userinfo) - (select count(*) from userinfo where id <= 10000) as count;

執行圖示:
4.2.count2.png

分析圖示:
4.2.count3.png

5.3.group by和order by

group byorder by的列儘可能相同,這樣能夠避免filesort

-- 5.3.group by和order by的列儘可能相同,這樣能夠避免filesort
explain
    select *
    from students
    group by name
    order by work;

explain
    select *
    from students
    group by name
    order by name;

-- 加where條件也同樣
explain
    select *
    from students
    where name like '小%'
    group by age
    order by work;

-- PS:通常group by和order by的列都和where索引列相同(不一致也只會使用一個索引)
explain
    select *
    from students
    where name like '小%' and age>20
    group by name
    order by name;

-- where後面的索引列和`order by|group by`後面的索引列不一致
-- id和email都是索引,但只用了一個索引
explain
    select *
    from users
    where id < 10
    order by email;

圖示:
4.3.orderby.png

PS:不一致也只會使用一個索引(在索引誤區有詳細說明)

5.4.用鏈接查詢來代替子查詢

通常來講都是用鏈接查詢來代替子查詢,有些時候子查詢更方便(具體看業務吧)

-- 用exists代替in?MySQL查詢優化器針對in作了優化(改爲了exists,當users表越大查詢速度越慢)
explain
    select *
    from students
    where name in (select username from users where id < 7);

-- ==> 等同於:
explain
    select *
    from students
    where exists(select username from users where username = students.name and users.id < 7);

-- 真正改進==>用鏈接查詢代替子查詢
explain
    select students.*
    from students
             inner join users on users.username = students.name and users.id < 7;

-- 等效寫法:這個tmp是臨時表,是沒有索引的,若是須要排序能夠在()裏面先排完序
explain
    select students.*
    from students
             inner join (select username from users where id < 7) as tmp on students.name = tmp.username;

圖示:(內部已經把in轉換成exists了,因此改不改寫無所謂了)
4.4.子查詢.png

5.5.★limit優化★

limit offset,N:mysql並非跳過offset行,而後取N行,而是取offset+N行,而後放棄前offset行,返回N

  • PS:offset越大效率越低(你去翻貼的時候,頁碼越大通常越慢)
知識點

爲了更加的直觀,咱們引入一下profiling

-- 查看profiling系統變量
show variables like '%profil%';
-- profiling:開啓SQL語句剖析功能(開啓以後應爲ON)

-- 來查看是否已經啓用profile
select @@profiling;

-- 啓動profile(當前會話啓動)
set profiling = 1; -- 0:未啓動,1:啓動

show profiles; -- 顯示查詢的列表

show profile for query 5; -- 查看指定編號查詢的詳細信息

輸出:

MariaDB [dotnetcrazy]> show variables like '%profil%';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| have_profiling         | YES   |
| profiling              | OFF   |
| profiling_history_size | 15    |
+------------------------+-------+
3 rows in set (0.002 sec)

MariaDB [dotnetcrazy]> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set (0.000 sec)

MariaDB [dotnetcrazy]> set profiling = 1;
Query OK, 0 rows affected (0.000 sec)
正文

上面設置完後,分別執行下面SQL:

select * from userinfo limit 10,10;
select * from userinfo limit 1000,10;
select * from userinfo limit 100000,10;
select * from userinfo limit 1000000,10;
select * from userinfo limit 10000000,10;

輸出:

+----------+------------+------------------------------------------+
| Query_ID | Duration   | Query                                    |
+----------+------------+------------------------------------------+
|        1 | 0.00060250 | select * from userinfo limit 10,10       |
|        2 | 0.00075870 | select * from userinfo limit 1000,10     |
|        3 | 0.03121300 | select * from userinfo limit 100000,10   |
|        4 | 0.30530230 | select * from userinfo limit 1000000,10  |
|        5 | 3.03068020 | select * from userinfo limit 10000000,10 |
+----------+------------+------------------------------------------+

圖示:
4.4.profiles.png

解決方法
  1. 業務上解決,eg:不準翻頁超過100(通常都是經過搜索來查找數據)
    • PS:百度搜索頁面也只是最多翻到76
  2. 使用where而不使用offset
    • id完整的狀況:eg:limit 5,3 ==> where id > 5 limit 3;
    • PS:項目裏面通常都是邏輯刪除,id基本上算是比較完整的
  3. 覆蓋索引+延遲關聯:經過使用覆蓋索引查詢返回須要的主鍵,再根據主鍵關聯原表得到須要的數據
    • 使用場景:好比主鍵爲uuidid不連續(eg:部分數據物理刪除了等等)

說太空洞,演示下就清楚了:

-- 全表掃描
explain
    select *
    from userinfo
    limit 10000000,10; -- 3s

-- 先range過濾了一部分
explain
    select *
    from userinfo
    where id > 10000000
    limit 10; -- 20ms

-- 內部查詢使用了索引覆蓋
explain
    select *
    from userinfo
             inner join (select id from userinfo limit 10000000,10) as tmp
                        on userinfo.id = tmp.id; -- 2s

分析圖示:
4.5.limit.png

查詢圖示:
4.5.limit2.png

擴展:索引誤區和冗餘索引

1.索引誤區

不少人喜歡把where條件的經常使用列上都加上索引,可是遺憾的事情是:獨立的索引只能同時用上一個

  • PS:在實際應用中每每選擇組合索引

別不信,來驗證一下就知道了:

-- id和email都是索引,可是隻能使用一個索引(獨立的索引只能同時用上一個)
-- id的key-len=4(int4個字節)
-- email的key-len=152(50*3(utf8下每一個字符佔3位)+2(varchar須要額外兩個字節存放)==>152)

-- 1.惟一索引和主鍵:優先使用主鍵
explain
    select * from users where id = 4 and email = 'xiaoming@qq.com';

-- 2.組合索引和主鍵:優先使用主鍵
explain
    select * from users where id=4 and createtime='2019-02-16 17:10:29';

-- 3.惟一索引和組合索引:優先使用惟一索引
explain
    select * from users where createtime='2019-02-16 17:10:29' and email='xiaoming@qq.com';

-- 4.組合索引和通常索引:優先使用組合索引
-- create index ix_users_datastatus on users(datastatus);
-- create index ix_users_username_password on users(username,password);
explain
    select * from users where datastatus=1 and username='小明';
-- 刪除臨時添加的索引
-- drop index ix_users_datastatus on users;
-- drop index ix_users_username_password on users;

圖示:
5.2個索引.png

PS:根據測試得知,一次只能使用1個索引。索引優先級:主鍵 > 惟一 > 組合 > 普通

2.冗餘索引

舉個標籤表的例子:

create table tags
(
    id         int unsigned auto_increment primary key,
    aid        int unsigned not null,
    tag        varchar(25)  not null,
    datastatus tinyint      not null default 0
);
insert into tags(aid,tag,datastatus) values (1,'Linux',1),(1,'MySQL',1),(1,'SQL',1),(2,'Linux',1),(2,'Python',1);

select id, aid, tag, datastatus from tags;

輸出:

+----+-----+--------+------------+
| id | aid | tag    | datastatus |
+----+-----+--------+------------+
|  1 |   1 | MySQL  |          1 |
|  2 |   1 | SQL    |          1 |
|  3 |   2 | Linux  |          1 |
|  4 |   2 | Python |          1 |
+----+-----+--------+------------+

實際應用中可能會根據tag查找文章列表,也可能經過文章id查找對應的tag列表

項目裏面通常是這麼創建索引(冗餘索引):index(文章id,tag),index(tag,文章id),這樣在上面兩種狀況下能夠直接用到覆蓋索引

create index ix_tags_aid_tag on tags(aid,tag);
create index ix_tags_tag_aid on tags(tag,aid);

select tag from tags where aid=1;
select aid from tags where tag='Linux';

3.修復碎片

這邊簡單說下,下一章應該還會繼續說運維相關的知識

數據庫表使用時間長了會出現碎片,能夠按期修復一下(不影響數據):optimize table users;

修復表的數據以及索引碎片會把數據文件整理一下,這個過程相對耗費時間(數據量大的狀況下)通常根據狀況選擇按周|月|年修復一下

PS:能夠配合crontab(定時任務)使用:

  • 使用命令:crontab -e***** 命令 [ > /dev/null 2>&1 ]
    • 5個*的含義
    • 從定向知識:
      • >> /xx/日誌文件:輸出重定向到日記文件(不包含錯誤信息)
      • >> /xx/日誌文件 2>&1:輸出信息包括錯誤信息
      • > /dev/null 2>&1:出錯信息重定向到垃圾桶(黑洞)
    • 舉幾個栗子:
      • 21*** xxx ==> 天天 1:02 執行 xxx命令
      • 5921*** xxx ==> 天天 21::59 執行 xxx命令
      • */*1*** xxx ==> 每1小時 執行一次xxx命令
        • 定時任務以*/開頭

課後拓展:

【推薦】一步步分析爲何B+樹適合做爲索引的結構
https://blog.csdn.net/weixin_30531261/article/details/79312676

善用mysql中的FROM_UNIXTIME()函數和UNIX_TIMESTAMP()函數
https://www.cnblogs.com/haorenergou/p/7927591.html

【推薦】MySQL crc32 & crc64函數 提升字符串查詢效率
https://www.jianshu.com/p/af6cc7b72dac

MySQL優化之profile
https://www.cnblogs.com/lizhanwu/p/4191765.html
相關文章
相關標籤/搜索