schema與資料型別優化-高效能mysql
總結作為開發人員重點注意的內容!這是一篇有關高效能MYSQL第四章schema相關的筆記。
0.前言
在專案中,資料庫表列有兩個text欄位,用來儲存大文字,在資料規模達到40萬後,如果查詢沒命中索引,發現耗時會達到5s以上,在表中刪除這兩個大欄位後,就算全表掃描,耗時也不過0.6s左右。
解決方案:
- 將這類不常參加查詢的大欄位放入詳情表中,但對現有程式影響過大(懶)。
- 查詢欄位統統加上索引。
1.選擇合適的資料型別
- 選擇合適的資料範圍,能用 tinyint 不用 int。
- 欄位越簡單越好。儲存時間使用datetime而不是字串,儲存ip用整數而不用字串。
- 儘量避免null。null 會使索引更加複雜,null改為not null效能提升較小,但設計時,邏輯上不可能為null的值,最好加上not null限制。
datetime 和 timestamp 比較
相同點: 都能儲存日期、時間,精確到秒。
不同點: timestamp 只佔 datetime 一半儲存空間,具備時區功能,同時允許的時間範圍小得多。
1.1.1 整數型別
型別及儲存空間
- tinyint(1位元組)
- smallint(2位元組)
- mediumint(3位元組)
- int(4位元組)
- bigint(8節字)
int(11) 其中11只是指定顯示寬頻,對儲存空間無任何影響。
可以指定型別為unsinged,但java不支援unsinged, 使用相同型別可能會有麻煩。
1.1.2 實數型別
精確型別:decimal, 不精確型別: double, float。
decimal 支援儲存和計算的精確。計算效能比浮點型別差一點。
decimal(11,4)表示支援4位精確小數,整數部分最多隻能有7位數字。
decimal型別4個位元組儲存9個數字,decimal(18,9)佔用9個位元組,前面9位整數佔4個位元組,小數點1個位元組,後面小數佔4個位元組。
mysql5.0版本及以上,decimal 最多儲存65個數字。
浮點型別在表示同樣範圍,通常比decimal使用更少的空間。
在資料量比較大的情況下,可以用bigint代替decimal, 將小數的位數乘以相應的倍數轉為bigint,可避免不精確的問題。
1.1.3字串型別
varchar 和char
varchar儲存可變長的字串,使用額外的1個或兩個位元組額外儲存空間,表示字串的長度。
varchar在table指定row_format=fixed情況下,也是定長的,這會導致空間的浪費。
varchar(256)使用一個位元組儲存字元字元數。varchar(1000)使用兩個位元組儲存字元數。
update varchar欄位可能會導致列變得更長,innodb儲存引擎會導致出現分裂頁的情況出現,myisam將行拆分成不同的段儲存。
varchar適用場景:最長字串比平均字串長度長很多。
char是定長的,不容易產生碎片,適用於儲存密碼或md5值, 或表示性別,F/M, varchar(1)需要兩個位元組,char(1)需要一個位元組。
char 會截斷末尾的空格。
varchar(5)和varchar(100)都能儲存5個字元,但varchar(100)效能會比varchar(5)糟糕,所以最好選擇合適的長度。
text與blob型別
text儲存大文字,blob儲存二進位制資料。
大文字有關的型別: tinytext, smalltext, text, mediumtext, longtext。
二進位制有關的型別: tinyblob, smallbolob, blob, mediumblob, longblob。
當text,blob太大時,會作為一個物件單獨儲存,行中使用指標指向資料位置。
個人建議少用或不用這種型別,或拆除到詳情表,減少對查詢帶來影響
enum型別
欄位定義:animal enum("dog", "fish", "cat")
enum 實際儲存為整數,並在.frm檔案中儲存字串-整數
對應關係。
看個列子更容易明白:
create table enum_test(
id int auto_increment,
animal enum("dog", "fish", "cat"),
primary key(id)
);
insert into enum_test(animal)
values
("dog"),
("cat"),
("dog"),
("fish")
;
對該表進行查詢:
select animal+0, animal
from enum_test
order by animal
;
可以看到排序是按照整數排序的,即定義的順序進行排序,而不是字串序。
解決方案:定義時按照字串序定義。
自定義順序排序使用field函式,但這樣將無法使用索引:
select animal+0, animal
from enum_test
order by field(animal, "cat", "fish", "dog");
欄位join效率比較: enum join enum > varchar join varchar > enum join varchar = varchar join enum。
使用alter talbe 在列舉型別中新增字串時,會重建整個表,除非總是在末尾新增值。
壞處:當列舉值為數字時,很容易發生困惑,儘量避免這種情況。
1.1.4 時間型別
datetime型別:
- 儲存範圍大: (1001年-9999年)精度為秒,將值儲存為 YYYYMMDDHHMMSS的大整數中,佔用八位元組。
timestamp:
- 儲存範圍小:(1970年-2038年),精度為秒,儲存從1970-1-1年以來的秒數,佔用4位元組。
- unix_timestamp()支援日期->秒數, from_unixtime()支援秒數->日期。
- 具備時區概念,在mysql伺服器,作業系統,連線串都可以指定時區,推薦在連線串指定時區:(jdbc)serverTimezone=Asia/Shanghai
- 插入時沒有指定會使用當前時間作為預設值
不推薦自己使用整數值來儲存時間,這和內部儲存沒什麼差別,沒帶來什麼好處卻多了額外的處理邏輯。
書中推薦使用timestamp, 個人推薦使用datetime,timestamp花活太多。
1.1.5 位資料型別
bit型別
bit列可以儲存多個true/false值,bit(17)可以儲存17個true/false值,myisam 使用一個位元來儲存一個單獨的true/false值,innodb使用最小的整數值來儲存bit列的值。
myisam: bit(17)使用17個位元儲存。
innodb: tinyint 即可儲存。
高效能mysql認為應該慎用這個型別,所以就不深究了。
set 型別
好處: 儲存使用打包的位儲存,佔用空間少。
壞處: 無法通過索引查詢,修改列定義代價較高:需要alter table。
許可權控制使用例子:
create table acl_test(
id int not null auto_increment,
perms set('can_read', 'can_write', 'can_delete'),
primary key(id)
);
insert into acl_test(perms)
values
('can_read,can_write'),
('can_read,can_delete')
;
select *
from acl_test
where find_in_set('can_write', perms)
;
查詢結果:
1.1.6 主鍵及其他
主鍵最好選擇自增id為主鍵,對資料效能比較好,同時相對應orm也支援,可能存在某些orm層框架不支援聯合主鍵的情況。
ip與int轉換函式: inet_aton(), inet_ntoa()。
1.2 schema錯誤設計
- 列太多(幾千個列)
- join 太多,mysql 限制最多可以join 61張表,實際為了效能考慮,最好一次查詢join不超過12張表
- 錯誤使用列舉, 如一個列舉欄位儲存國家,列舉值有幾十上百個, 更好的解決辦法是使用關聯表去儲存國家資訊,用整數去對映。