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


1.5.查詢的藝術

上期回顧:https://www.cnblogs.com/dotnetcrazy/p/10399838.html

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

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

1.5.1.索引

大方向:減少冗余索引,避免重復(無用)索引

1.概念

大一統分類:

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

PS:索引通常做查詢條件的字段(索引是在存儲引擎級別實現的)

常用分類:

  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.往期回顧

先回顧下上節課內容:

手寫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


免責聲明!

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



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