面試官:你是如何優化MySQL

概述

爲什麼要優化:
應用的吞吐量一般出現在數據庫處理速度上
隨着應用的使用數據量不斷增多,數據庫處理壓力主鍵增大
關係型數據庫的數據存儲在磁盤上,讀寫數據較慢(與內存數據庫比較)

優化手段:

  • 第一步是表字段的設計,考慮更優的存儲計算
  • 利用好MySQL自身提供的功能,如 存儲引擎的選擇、索引等
  • 橫向擴展:MySQL讀寫分離
  • SQL語句的優化(收效甚微)

字段設計

字段類型的選擇,設計規範,範式,常見設計案例

在設計表字段建議,使用8字節的主鍵bigint,而不是直接使用int來做主鍵

varchar 不存的時候不佔空間,存多長數據就佔多少空間

儘量使用整型表示字符串,這樣能根節省存儲空間
存儲ip的方式:

INET_ATON(str),address to number
INET_NTOA(number),number to address

數據庫字段:定長和非定長數據類型的選擇
decimal不會損失精度,存儲空間會隨數據的增大而增大。double佔用固定空間,較大數的存儲會損失精度。非定長的還有varchar、text

定點數decimal:price decimal(8,2)有2位小數的定點數,定點數支持很大的數(甚至是超過int,bigint存儲範圍的數)

小單位大數額避免出現小數:元->分

原則:儘可能選擇小的數據類型和指定短的長度

儘可能使用 not null
null字段的處理要比null字段的處理高效些!且不需要判斷是否爲null
null在MySQL中,不好處理,存儲需要額外空間,運算也需要特殊的運算符。如select null = nullselect null <> null(<>爲不等號)有着同樣的結果,只能通過is nullis not null來判斷字段是否爲null。
如何存儲?MySQL中每條記錄都需要額外的存儲空間,表示每個字段是否爲null。因此通常使用特殊的數據進行佔位,比如int not null default 0string not null default ‘’

字段註釋要完整,見名知意

單表字段不宜過多:二三十個就極限了

可以預留字段
在使用以上原則之前首先要滿足業務需求

命名規則
表名:
1、要用前綴,但不要用無意義的前綴
2、下劃線分隔
3、全小寫

列名規則:
1、一般不用前綴(當和關鍵詞衝突的可以考慮加前綴區別)
2、下劃線分隔
3、全小寫不管是表名設計還是列名設計,都不要使用拼音來命名,過一段時間就完全不記得了,就用英文,即使英語不好設計的時候也建議設置爲英文。

符合三範式數據庫表

1NF: 列不可分。每一列都是不可分割的基本數據項,如這樣的設計就不合理,姓名(王五,wangwu)
2NF: 1NF的基礎上面,非主屬性完全依賴於主關鍵字,如學生姓名(非主屬性)就是依賴於學號(主屬性)的。目的是爲了 消除對主鍵的部分依賴。
3NF: 屬性不依賴於其它非主屬性 , 消除傳遞依賴,如這樣的設計就不合理,學號做主鍵,學生課程表(學號=課程),當學號修改,對應的課程表也需要修改,這就是屬於傳遞依賴
BCNF: 符合3NF,每個表中只有一個候選鍵
4NF: 沒有多值依賴
由於學號不能做主鍵,那用什麼做主鍵?首先就有這樣的規則:不要用業務規則來做主鍵,主鍵就應該和業務無關。
如經常用的的order_no(業務訂單號),即使是唯一的,也不建議做主鍵的,容易產生傳遞依賴的問題,這樣就不符合第三範式了。

存儲引擎選擇

關於存儲引擎可以看這篇博客:mysql存儲引擎
正常使用 InnoDB 就好了。主要是記住他們之間的差異

索引優化

關於索引可以直接看我這篇博客:索引使用詳解

水平切分和垂直切分

水平分割:通過建立結構相同的幾張表分別存儲數據

垂直分割:將經常一起使用的字段放在一個單獨的表中,分割後的表記錄之間是一一對應關係。

分表原意

  • 爲數據庫減壓
  • 分區算法侷限
  • 數據庫支持不完善(5.1之後mysql才支持分區操作)

id重複的解決方案

  • 借用第三方應用如memcache、redisid自增器
  • 單獨建一張只包含id一個字段的表,每次自增該字段作爲數據記錄的id

MySql讀寫分類實現橫向擴展

橫向擴展:從根本上(單機的硬件處理能力有限)提升數據庫性能 。由此而生的相關技術:讀寫分離、負載均衡

Centos7搭建mysql集羣 讀寫分離 主從複製
springBoot 使用AOP完成多數據源配置。寫用主庫,讀用從庫

典型Sql

線上DDL

DDL(Database Definition Language)是指數據庫表結構的定義(create table)和維護(alter table)的語言。在線上執行DDL,在低於MySQL5.6版本時會導致全表被獨佔鎖定,此時表處於維護、不可操作狀態,這會導致該期間對該表的所有訪問無法響應。但是在MySQL5.6之後,支持Online DDL,大大縮短了鎖定時間。
優化技巧是採用的維護表結構的DDL(比如增加一列,或者增加一個索引),是copy策略。思路:創建一個滿足新結構的新表,將舊錶數據逐條導入(複製)到新表中,以保證一次性鎖定的內容少(鎖定的是正在導入的數據),同時舊錶上可以執行其他任務。導入的過程中,將對舊錶的所有操作以日誌的形式記錄下來,導入完畢後,將更新日誌在新表上再執行一遍(確保一致性)。最後,新表替換舊錶(在應用程序中完成,或者是數據庫的rename,視圖完成)。
但隨着MySQL的升級,這個問題幾乎淡化了。

數據庫多語句導入

在恢復數據時,可能會導入大量的數據。此時爲了快速導入,需要掌握一些技巧:

1、導入的時候 先禁用索引和約束

alter table table-name disable keys

待數據導入完成之後,再開啓索引和約束,一次性創建索引

alter table table-name enable keys

2、數據庫如果使用的引擎是Innodb,那麼它默認會給每條寫指令加上事務(這也會消耗一定的時間),因此建議先手動開啓事務,再執行一定量的批量導入,最後手動提交事務

START TRANSACTION; 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  VALUES ('0', 'userid_0', 'content_0', 0); 
INSERT INTO `insert_table` (`datetime`, `uid`, `content`, `type`) 
  VALUES ('1', 'userid_1', 'content_1', 1); 
... 
COMMIT; 

3、如果批量導入的SQL指令格式相同只是數據不同,那麼你應該先prepare預編譯一下,這樣也能節省很多重複編譯的時間。

limit offset,rows

儘量保證不要出現大的offset,比如limit 10000,10相當於對已查詢出來的行數棄掉前10000行後再取10行,完全可以加一些條件過濾一下(完成篩選),而不應該使用limit跳過已查詢到的數據。這是一個offset做無用功的問題。對應實際工程中,要避免出現大頁碼的情況,儘量引導用戶做條件過濾。

select * 要少用

即儘量選擇自己需要的字段select,但這個影響不是很大,因爲網絡傳輸多了幾十上百字節也沒多少延時,並且現在流行的ORM框架都是用的select *,只是我們在設計表的時候注意將大數據量的字段分離,比如商品詳情可以單獨抽離出一張商品詳情表,這樣在查看商品簡略頁面時的加載速度就不會有影響了。

order by rand()不要用

它的邏輯就是隨機排序(爲每條數據生成一個隨機數,然後根據隨機數大小進行排序)。如select * from student order by rand() limit 5的執行效率就很低,因爲它爲表中的每條數據都生成隨機數並進行排序,而我們只要前5條。
解決思路:在應用程序中,將隨機的主鍵生成好,去數據庫中利用主鍵檢索。

count(*)

在MyISAM存儲引擎中,會自動記錄表的行數,因此使用count(*)能夠快速返回。而Innodb內部沒有這樣一個計數器,需要我們手動統計記錄數量,解決思路就是單獨使用一張表:
在這裏插入圖片描述

limit 1

如果可以確定僅僅檢索一條,建議加上limit 1,其實ORM框架幫我們做到了這一點(查詢單條的操作都會自動加上limit 1)。

Join語句使用

能不能使用join語句?
1、如果可以使用Index Nested-Loop Join算法,也就是說可以用上被驅動表上的索引,其實是沒問題的;
2、如果使用Block Nested-Loop Join算法,掃描行數就會過多。尤其是在大表上的join操作,這樣可能要掃描被驅動表很多次,會佔用大量的系統資源。所以這種join儘量不要用。

所以你在判斷要不要使用join語句時,就是看explain結果裏面,Extra字段裏面有沒有出現“Block Nested Loop”字樣。

如果要使用join,應該選擇大表做驅動表還是選擇小表做驅動表?
1、如果是Index Nested-Loop Join算法,應該選擇小表做驅動表;
2、如果是Block Nested-Loop Join算法:

  • 在join_buffer_size足夠大的時候,是一樣的;
  • 在join_buffer_size不夠大的時候(這種情況更常見),應該選擇小表做驅動表。

所以,這個問題的結論就是,總是應該使用小表做驅動表。

在決定哪個表做驅動表的時候,應該是兩個表按照各自的條件過濾,過濾完成之後,計算參與join的各個字段的總數據量,數據量小的那個表,就是“小表”,應該作爲驅動表。

典型的服務器配置

以下的配置全都取決於實際的運行環境

  • max_connections,最大客戶端連接數
mysql> show variables like 'max_connections';
+-----------------+-------+
| Variable_name   | Value |
+-----------------+-------+
| max_connections | 151   |
+-----------------+-------+
  • table_open_cache,表文件句柄緩存(表數據是存儲在磁盤上的,緩存磁盤文件的句柄方便打開文件讀取數據)
mysql> show variables like 'table_open_cache';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| table_open_cache | 2000  |
+------------------+-------+
  • key_buffer_size,索引緩存大小(將從磁盤上讀取的索引緩存到內存,可以設置大一些,有利於快速檢索)
mysql> show variables like 'key_buffer_size';
+-----------------+---------+
| Variable_name   | Value   |
+-----------------+---------+
| key_buffer_size | 8388608 |
+-----------------+---------+
  • innodb_buffer_pool_sizeInnodb存儲引擎緩存池大小(對於Innodb來說最重要的一個配置,如果所有的表用的都是Innodb,那麼甚至建議將該值設置到物理內存的80%,Innodb的很多性能提升如索引都是依靠這個)
mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| innodb_buffer_pool_size | 8388608 |
+-------------------------+---------+
  • innodb_file_per_tableinnodb中,表數據存放在.ibd文件中,如果將該配置項設置爲ON,那麼一個表對應一個ibd文件,否則所有innodb共享表空間)
發佈了167 篇原創文章 · 獲贊 59 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章