高性能MySql之字段設計

前言

導致數據庫慢的原因有呢些?

  • 頻繁的磁盤操作
  • 數據量過大

針對原因我們可以選擇優化的幾個方面

  • 設計數據庫數據表時選擇正確的字段以及存儲引擎
  • 利用好mysql服務器提供的功能(索引,分區等等)
  • 橫向擴展,負載均衡,讀寫分離

字段設計

結論:

  • 越小越好,夠用就好
更小的數據類型通常更快,因爲他們佔用更少的磁盤、內存和CPU緩存,並且處理時有需要的CPU週期也更少
如果無法確定哪個數據類型是最好的,就選擇你認爲不會超過範圍的最小類型
  • 簡單就好,沒null最好
簡單數據類型的操作通常需要更少的CPU週期,例如整型比字符操作代價更低,因爲字符集和校對規則使字符比較比整型比較更復雜
當可爲null的列被索引時,每個索引記錄需要一個額外的字節,會佔用更多的存儲空間

整數類型

  • TINYINT:8位,範圍-2^(8-1) ~ 2^(8-1)即-256~256
  • SMALLINT:16位,範圍-2^(16-1) ~ 2^(16-1)即-65536~65536
  • MEDIUMINT:24位,範圍-2^(24-1) ~ 2^(24-1)即-16,777,216‬~16,777,216‬
  • INT:32位,範圍-2^(32-1) ~ 2^(32-1)即-4,294,967,296‬~4,294,967,296‬
  • BIGINT:64位,範圍-2^(64-1) ~ 2^(64-1)即-18,446,744,073,709,551,616~18,446,744,073,709,551,616

注:整數類型有可選的UNSIGNED屬性,表示不允許負值,可使正數上限提高一倍

儘量使用整形表示字符串(例如表示枚舉),存儲空間固定,佔用空間少,運算速度快

實數類型

FLOAT(4個字節)和DOUBLE(8個字節)(定長型)支持使用標準的浮點運算進行近似計算,存在精度丟失問題,佔據固定的存儲空間,運算效率高,無論數據多大,佔用的空間是固定的,所以當超過最大空間時會導致精度丟失
DECIMAL(變長型)用於存儲精確地小數,不會導致數據丟失,佔用的空間會隨着數據的增大而增大
DECIMAL(M,D)依賴於M(小數點之前最大位數)和D(小數點之後最大位數)的值,如果M>D,爲M+2否則爲D+2

整數運算沒有精度問題,小數運算存在精度問題,計算機無法將小數完全轉爲二進制,所以可以通過縮小單位通過整數運算體高精度

例:1.2萬元>>>>>12000元

字符串類型

VARCHAR與CHAR

  • VARCHAR類型用於存儲可變長字符串,他比定長型更節省空間,因爲他僅使用必要的空間,越短的字符串佔用空間越少 ,比如varchar(10), 然後輸入abc三個字符,那麼實際存儲大小爲4個字節。
  • VARCHAR需要需要使用1或2個額外字節記錄字符串的長度,如果列的最大長度小於等於255,則使用一個字節表示,否則使用兩個字節
  • CHAR類型是定長的,即當定義的是char(10),輸入的是"abc"這三個字符時,它們佔的空間一樣是10個字節,包括7個空字節。當輸入的字符長度超過指定的數時,char會截取超出的字符。而且,當存儲char值時,MySQL是自動刪除輸入字符串末尾的空格。CHAR很適合存儲短的字符串,或者所有的值都接近一個長度(比如賬號密碼姓名)。對於非常短的列,CHAR比VARCHAR在存儲空間上更有效率。例如用CHAR(1)來存儲只有Y和N的值,CHAR(1)只需要一個字節,VARCHAR(1)需要2個字節,需要額外一個字節記錄長度
  • 所以,從空間上考慮,varchar較合適;從效率上考慮,用char合適。二者之間需要權衡。
QUESTION:使用VARCHAR(5)VARCHAR(200)存儲'hello'的空間開銷是一樣的,那麼使用短列更有優勢嗎?
是的!更長的列會消耗更多的內存,因爲MYSQL通常會分配固定大小的內存塊來保存內部值。
所以最好的策略就是分配真正需要的空間    

BLOB與TEXT

  • 他們都是爲了存儲很大的數據而設計的字符串數據類型,比如文章,或者bpm中的xml,分別採用二進制和字符串方式存儲
    • 字符類型:TINYTEXT,SMALLTEXT,TEXT,MEDIUMTEXT,LONGTEXT
    • 二進制類型:TINYBLOB,SMALLBLOB,BLOB,MEDIUMBLOB,LONGBLOB
  • 因爲BLOB類型存儲的是二進制數據,所以沒有排序規則和字符集,但可以存儲圖片,TEXT與VARCHAR類似,只能存儲純文本,只不過他能存儲的字符數更多
TIPS:
字符集:字符集是針對不同語言的字符編碼的集合,比如UTF-8字符集,GBK字符集,GB2312字符集等等,不同的字符集使用不同的規則給字符進行編碼   
排序規則:是在特定字符集的基礎上特定的字符排序方式,排序規則是基於字符集的,是對字符集在排序方式維度上的一個劃分。
    
排序規則是依賴於字符集的,一種字符集可以有多種排序規則,但是一種排序規則只能基於某一種字符集的

日期和時間類型

MYSQL存儲的最小時間粒度爲秒,但是他支持使用爲微秒級別的粒度進行臨時運算
  • DATETIME類型:範圍1001年到9999年,精度爲秒。他把日期和時間封裝到格式爲YYYYMMDDHHMMSS的整數中,與時區無關。使用8個字節的存儲空間

  • TIMESTAMP類型:保存了從1970年1月1日零點零分零秒以來的秒數。只使用4個字節的存儲空間,因此它的範圍比DATETIME小很多:只能表示從1970年到2038年,超出這個範圍則值記錄爲’0000-00-00 00:00:00’

    • TIMESTAP顯示的值也依賴於時區。因此存儲值爲0的TIMESTAP在美國東部時區顯示爲"1969-12-31 19:00:00".與格林尼治時間相差5個小時
  • DATE類型:就是日期,用於具有日期部分但沒有時間部分的值。date類型的字段,佔用3個字節。在數據庫中存儲數據格式爲:YYYY-MM-DD,它支持的範圍爲’1000-01-01’到’9999-12-31’,並且允許使用字符串或數字爲此列賦值。

  • TIME類型:time數據類型指的是具體的不包括日期的時間,佔用3個字節。以’hh:mm:ss’格式(或 'hhh:mm:ss’大小時數格式)顯示值 。TIME值的範圍可以從 '-838:59:59’到 ‘838:59:59’。小時部分可能會很大,因爲該TIME類型不僅可以用來表示一天中的某個時間(必須少於24小時),而且可以用來表示經過的時間或兩個事件之間的時間間隔(可能遠大於24小時,甚至是負的)。

    TIME值的範圍 是 '-838:59:59.000000’至 ‘838:59:59.000000’。

  • YEAR類型:YYYY類型用於表示年份值,佔用1個字節。以YYYY格式顯示值, 範圍 1901爲2155,和 0000。

位數據類型

MySQL有少數幾種存儲類型使用緊湊的位存儲數據。不管底層存儲格式如何處理,從技術上說都是字符型。

BIT:BIT(1)表示定義一個包含一位的長度,BIT(2)表示2個位,以此類推,BIT列最大長度是64位,MySQL把BIT當字符串處理,而不是數字,當檢索BIT(1)的值時,結果是一個包含二進制0或1的字符串,而不是ASCII碼的0或1,例如00111001,它的二進制等於57,檢索它時得到是一個字符碼爲57的字符,也就是ASCII碼57對應字符爲9。

整合JPA

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
	//定義了被標註字段在數據庫表中所對應字段的名稱;
    String name() default "";
	//表示該字段是否爲唯一標識,默認爲false。如果表中有一個字段需要唯一標識,則既可以使用該標記,也可以使用@Table標記中的UniqueConstraint。
    boolean unique() default false;
	//表示該字段是否可以爲null值,默認爲true。
    boolean nullable() default true;
	//表示在使用“INSERT”腳本插入數據時,是否需要插入該字段的值。(即插入時是否不忽略該字段)
    boolean insertable() default true;
	//表示在使用“UPDATE”腳本插入數據時,是否需要更新該字段的值。(即修改時是否不忽略該字段)
    boolean updatable() default true;
	//字段定義,一旦使用,jpa不會自動識別類型創建,注意sql語法
    String columnDefinition() default "";
	//表示當映射多個表時,指定表的表中的字段。默認值爲主表的表名。
    String table() default "";
	//表示字段的長度,當字段的類型爲varchar時,該屬性纔有效,默認爲255個字符。
    int length() default 255;
	//precision屬性和scale屬性表示精度,當字段類型爲double(或decimal)時,precision表示數值的總長度,scale表示小數點所佔的位數。
    int precision() default 0;

    int scale() default 0;
}
@Getter
@Setter
@ToString
@Table(name = "audit_log", indexes = {@Index(columnList = "cost_time")})
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class AuditLog extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @ApiModelProperty("用戶id")
    @Column(name = "user_id", columnDefinition = "SMALLINT(15) UNSIGNED COMMENT '用戶id'")
    private Integer userId;

    @ApiModelProperty("traceId")
    @Column(name = "trace_id", columnDefinition = "CHAR(20) COMMENT 'traceId'")
    private String uuid;

    @ApiModelProperty("服務端ip")
    @Column(name = "service_ip", columnDefinition = "CHAR(15) COMMENT '服務端ip'")
    private String serviceIp;

    @ApiModelProperty("用戶姓名")
    @Column(name = "admin_user", columnDefinition = "CHAR(10) COMMENT '用戶姓名'")
    private String adminUser;

    @ApiModelProperty("響應時間(毫秒)")
    @Column(name = "cost_time", columnDefinition = "SMALLINT(15) UNSIGNED COMMENT '響應時間(毫秒)'")
    private Integer costTime;

    @ApiModelProperty("請求地址")
    @Column(name = "uri", columnDefinition = "varchar(255) COMMENT '請求地址'")
    private String uri;

    @ApiModelProperty("入參")
    @Column(name = "param_json", columnDefinition = "varchar(255) COMMENT '入參'")
    private String paramJson;

    @ApiModelProperty("請求方式")
    @Column(name = "method", columnDefinition = "CHAR(8) COMMENT '請求方式'")
    private String method;

    @ApiModelProperty("用戶地址")
    @Column(name = "ip", columnDefinition = "varchar(255) COMMENT '用戶地址'")
    private String ip;

    @ApiModelProperty("事件時間")
    @Column(name = "event_time", columnDefinition = "DATETIME COMMENT '事件時間'")
    private Date eventTime;

    @ApiModelProperty("異常堆棧")
    @Column(name = "exception", columnDefinition = "varchar(255) COMMENT '異常堆棧'")
    private String exception;

    @ApiModelProperty("事件級別")
    @Column(name = "event_level", columnDefinition = "TINYINT(1) UNSIGNED COMMENT '事件級別'")
    private Integer eventLevel;

    @ApiModelProperty("事件類型")
    @Column(name = "event_type", columnDefinition = "TINYINT(1) UNSIGNED COMMENT '事件類型'")
    private Integer eventType;

    @ApiModelProperty("事件模塊")
    @Column(name = "event_module", columnDefinition = "SMALLINT(10) UNSIGNED COMMENT '事件類型'")
    private Integer eventModule;

    @ApiModelProperty("事件描述")
    @Column(name = "event_description", columnDefinition = "varchar(255) COMMENT '事件描述'")
    private String eventDescription;

    @ApiModelProperty("出參結果")
    @Column(name = "result_json", columnDefinition = "varchar(500) COMMENT '出參結果'")
    private String resultJson;

    @ApiModelProperty("事件結果")
    @Column(name = "event_result")
    private Boolean eventResult;
}

越小越好,夠用就好

越小越好,夠用就好

越小越好,夠用就好

參考文獻

1.《高性能MYSQL》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章