Mysql優化(出自官方文件) - 第九篇(優化資料庫結構篇)
目錄
- Mysql優化(出自官方文件) - 第九篇(優化資料庫結構篇)
- 1 Optimizing Data Size
- 2 Optimizing MySQL Data Types
- 3 Optimizing for Many Tables
- 4 Internal Temporary Table Use in MySQL
Mysql優化(出自官方文件) - 第九篇(優化資料庫結構篇)
1 Optimizing Data Size
通常來講,定義表的時候,一個合理的資料型別,往往意味著更少的儲存空間,更少的磁碟I/O,索引掃描也會更快,接下來將從五個方面來介紹如何進行這種優化:
Table Columns
- 儘量選擇更合適的資料型別,比如說,對於
integer
型別來講,選擇MEDIUMINT
比INT
能節約25%的儲存空間。 - 如果某列不為
NULL
,那麼儘可能的將其定義為NOT NULL
,這樣子,不僅能節約1bit
的儲存空間,更重要的是,Mysql的處理速度能夠得到大大的提高,因為這樣子Mysql就不需要處理NULL
的情況,且索引的處理也能得到加速。
- 儘量選擇更合適的資料型別,比如說,對於
Row Format
InnoDB
預設使用DYNAMIC
的行格式,如果要使用其他的格式,可以通過配置innodb_default_row_format
的方式來指定,或者在建立表/更改表的時候顯示指定行格式。為了使用緊湊型的行格式,可以指定
COMPACT
DYNAMIC
, andCOMPRESSED
,這三種格式一方面可以減少儲存空間,另外一方面,也可以大大提高cache命中率,減少磁碟I/O。- 使用
ROW_FORMAT=COMPRESSED
格式,InnoDB壓縮表支援讀和寫,而MyISAM則只支援讀 對於MyISAM表,如果沒有指定變長的列(如
VARCHAR
,TEXT
或者BLOB
),那麼將會預設使用固定長度的型別,對於MyISAM
來講,固定長度的列速度更快,當然代價是會浪費一部分空間。
Indexes
primary key
應該儘可能的短,這樣子查詢的時候效率更高,在InnoDB
中,由於每一個primary key
在二級索引中都會被複制一份,當擁有很多的二級索引時,這就顯得很有必要。- 只有需要的時候再去建立索引,索引雖然可以加快訪問速度,但是同時也會增加
insert
和update
的開銷;如果訪問一個表的時候,經常訪問多列的組合,那麼在這個組合上建立索引,並且儘量保證訪問頻次最高的列在最前面,千萬不要分別在這些列上建立索引。 - 通常來講,有很大的可能一個
string
型別的列有著不同的字首,這個時候建議只在這些字首上建立索引,這樣子不僅可以增大索引的cache
命中率,還可以減少磁碟I/O
Joins
- 在某些場景下,將一個經常被掃描的表分成兩個是有較大好處的,特別是當這個表是一個
dynamic-format
表且可以使用一個更小的static format
表來索引它的時候。 - 將需要
join
的兩個表的列設定為類似的資料型別,可以加快join
的速度。 - 使用簡單的名字,這樣子當查詢多個表的時候可以使用一樣的名字,並且能夠簡化
join
語句,比如:在一個customer
表中,使用name
,而不要使用customer_name
。
- 在某些場景下,將一個經常被掃描的表分成兩個是有較大好處的,特別是當這個表是一個
Normalization
- 通常來講,儘量保證所有的資料是非冗餘的(參考第三正規化的定義),避免重複的使用冗長的值(比如
names
或者addresses
之類的),而是選擇為他們賦一個唯一的ID,這樣子在多個小表中就可以重複使用這些ID,在join
的時候儘量使用ID來進行join
,這樣子就可以加快訪問速度。 - 如果速度的優先順序非常高,比磁碟空間和維護多個重複資料的優先順序還高,這個時候,就不需要嚴格遵循這些規範,可以適當的冗餘一些資訊或者建立一個類似於
summary
的表來加快訪問速度。
- 通常來講,儘量保證所有的資料是非冗餘的(參考第三正規化的定義),避免重複的使用冗長的值(比如
2 Optimizing MySQL Data Types
Optimizing for Numeric Data
- 對於唯一的ID或者其他可以用
string
和數字表達的值,儘量使用numeric
,因為相對於string
型別,numeric
佔用的位元組數更小,並且在處理的時候也會佔用更小的記憶體空間。 - 如果使用
numeric
資料,訪問的資料庫的速度要遠比訪問文字檔案的速度快,因為在資料庫中儲存的空間更加的緊湊。
- 對於唯一的ID或者其他可以用
Optimizing for Character and String Types
- 使用
BINARY
型別的callation
,速度更快 - 當比較不同列時,將這些列宣告為同種字符集和
collation
,可以避免型別之間的轉換。 - 當列小於8kb的時候,使用二進位制形式的
VARCHAR
來取代BLOB
型別,因為GROUP BY
和ORDER BY
有可能會建立臨時表,對於Mysql來講,如果沒有BLOB
欄位,那麼有可能會直接使用MEMORY
儲存引擎來建立臨時表。 - 如果一個表包含諸如
name
或者address
這樣的列,並且這些列的訪問頻次非常低,那麼,請將這些列分割到小表中,並使用join
來查詢,使用外來鍵來同步更新,因為當Mysql獲取某一行的時候,會讀取一個data block
,這麼做可以減少Mysql讀取block
的大小,從而減少磁碟I/O和記憶體佔用。 - 在
InnoDB
中,如果使用隨機值作為primary key
,那麼最好用當前的日期或者時間作為該列的字首,對於連續的primary key
,Mysql在物理上儲存的方式也是連續的,這樣子可以加快insert
和select
的速度。
- 使用
Optimizing for BLOB Types
- 當儲存一個非常大的
BLOB
列,且該列包含文字資料時,考慮壓縮它,但是如果表已經被設定為壓縮了,那麼不要這樣做。 - 類似於
string
型別的優化方式,如果BLOB
的訪問頻次不高,為了減少記憶體佔用,考慮將BLOB
分離為一個小表。 - 鑑於獲取
BLOB
型別列的效能需求非常不同於其他資料型別,考慮將只有BLOB
型別的表放在不同的儲存裝置或者其他的資料庫例項中,比如:因為BLOB
型別涉及到大量的順序讀寫,那麼將BLOB
儲存在傳統機械磁碟上速度將可能優於SSD裝置。 - 當需要測試一段非常長的文字是否相同時,考慮使用一個專門的列來儲存這段文字的
HASH
值(使用Mysql
的MD5()
和CRC32()
函式來實現),並且對其建立索引,因為雜湊函式可能會產生相同的結果,所以,在查詢的時候,除了檢測HASH列外
,還需要加上AND blob_column = long_string_value
來確保出現這種情況時的正確性,這種優化方式可以大大加快BLOB
型別列的處理速度。
- 當儲存一個非常大的
3 Optimizing for Many Tables
我們已經知道為了加快查詢速度,將資料分割為多個表是一種非常方便的優化方式,但是,當表的數量增大到上千個甚至上萬個時,優化方式將會有些不一樣。
How MySQL Opens and Closes Tables
使用
mysqladmin status
命令,可以看到如下結果:Uptime: 426 Running threads: 1 Questions: 11082 Reloads: 1 Open tables: 12
為了提高效能,Mysql針對每一個
session
都會open
一個table
,這樣子,如果有多個客戶端連線到Mysql,那麼這裡的open tables
可能比實際的表要多。(注意,MyISAM
是不一樣的,所有的session
共享檔案描述符)出現下面的情況,Mysql將會關閉一個沒有用的
table
,並且將其從cache
中移除:cache
滿了,此時有一個執行緒嘗試開啟不在cache
中的table
cache
中包含超過table_open_cache
的table
,此時cache
中有沒有被任何執行緒使用的table
- 當進行
table-flushing
操作時,通常是通過FLUSH TABLES
, 或者 mysqladmin flush-tables or mysqladmin refresh 觸發。
如果一個
table cache
滿了,但是此時有需要開啟一個table
,Mysql將會使用如下策略:- 那些沒有被使用的
table
將會被釋放,採用LRU
原則 - 如果一個
table
必須被開啟,但是cache
又滿了,此時,cache
會被臨時擴充,在擴充狀態下的cache
,一旦某個table
變成unused
狀態,那麼會被直接移除。
對於
MyISAM
表,該表沒被訪問一次,就會被開啟一次,也就是說如果有兩個執行緒訪問一樣的表或者說一張執行緒訪問了該表兩次,那麼該表會被open
兩次;第一次開啟表的時候需要兩個檔案描述符:第一個是資料file
,另外一個是索引file
,其中資料file
每個執行緒使用一個,索引file
所有執行緒共享。Disadvantages of Creating Many Tables in the Same Database
很容易會導致
table cache
不夠用,然後出現不斷開啟關閉的情況,降低效率。
4 Internal Temporary Table Use in MySQL
很多情況下,Mysql都會進行建立臨時表(記憶體表或者會下盤的表),這些條件如下(因為原文太長,所以這裡省略和合並部分條件):
- 絕大多數
union
語句,只有部分特殊情況才不會進行此操作(下面會進行討論)。 - 對於一些
views
的評估,如使用了TEMPTABLE
演算法,UNION
或者聚合函式 derived tables, common table expressions
,帶有子查詢的建立表操作或者semijoin
ORDER BY
和GROUP BY
的列不同,或者在join
中,ORDER BY
和GROUP BY
的列包含除了第一個表以外其他表的列。ORDER BY
並且帶有DISTINCT
可能會需要建立臨時表- 帶有
SQL_SMALL_RESULT
識別符號的語句,會使用一個記憶體臨時表,除非該語句包含必須建立on-disk
臨時表的部分。 INSERT ... SELECT
形式的語句。- 多表
UPDATE
操作,GROUP_CONCAT
或者COUNT(DISTINCT)
語句,window functions
對於一些查詢,Mysql無法使用in-memory
臨時表,這個時候就只能建立on-disk
臨時表,這些情況包括:
- 包含
BLOB
或者TEXT
列的表,然而,對於Mysql8.0.13,TempTable
這種in-memory
儲存引擎,已經支援了大的二進位制型別。 - 在
SELECT
中,如果使用了UNION
或者UNION ALL
,並且string
型別的列包含了超過512(對於二進位制來講單位是bytes
,對於非二進位制型別來講是字元數),那麼將無法使用in-memory
儲存引擎。 - 對於
SHOW COLUMNS
和DESCRIBE
,且使用了BLOB
型別的語句
某些情況下,UNION
也不一定會使用臨時表,這些情況包括:
- union型別是
UNION ALL
, notUNION
orUNION DISTINCT
. - 沒有全域性的
ORDER BY
語句 - 對於
{INSERT | REPLACE} ... SELECT ...
語句,union
不在最外層。
在具體實現時,內部in-memory
臨時表儲存在TempTable
或者MEMORY
儲存引擎,on-disk
臨時表儲存在InnoDB
中:
在Mysql8.0.16之前,可以通過internal_tmp_disk_storage_engine=MYISAM
引數來制定具體的儲存引擎,但是其或者之後,該引數就已經被拋棄了,使用者將無法配置使用哪種儲存引擎。
NOTE:
當使用MEMORY儲存引擎時,剛開始臨時表會被建立在記憶體中,但是當臨時表變得比較大了,Mysql會開始建立臨時檔案,並使用對映的方式對映到記憶體中。