MySQL(五)---- 選擇合適的數據類型

        每當創建一張數據表的時候我們就面臨着選擇什麼樣的數據類型,選多大的等等問題,很多人憑感覺選了類型估計了大小,但這麼做往往後期出錯或浪費空間,因此根據特性選擇合適的類型及大小很有必要。

一、CHAR 與 VARCHAR

       都用來存儲字符串,CHAR屬於固定長度的字符類型,VARCHAR屬於可變長度的字符類型,它們的保存和檢索方式不同。

從上表可以看出,固定長度的字符串存儲空間是固定的,不管內容如何,而變長字符串的存儲空間隨着內容有所變動;這裏需要注意一點,當字符串的長度超過給定的長度時,如果是非”嚴格模式“會截掉尾部多餘的長度(如上面最後一行);如果是”嚴格模式“,該值不會被保存且會出現錯誤提示。

        這兩種類型進行索引時也不相同,比如從表中查數據,CHAR類型會先刪除掉字符串尾部的空格然後再呈現出來,而VARCHAR則不會,例如:

mysql> create table vc (v varchar(4), c char(4));
Query OK, 0 rows affected (0.02 sec)

mysql> insert into vc values('ab  ','ab  ');
Query OK, 1 row affected (0.01 sec)

mysql> select concat(v,'+'),concat(c,'+') from vc;
+---------------+---------------+
| concat(v,'+') | concat(c,'+') |
+---------------+---------------+
| ab  +         | ab+           |
+---------------+---------------+
1 row in set (0.00 sec)

          對於字符串長度變化不大且對查詢速度又較高要求的數據可以考慮使用CHAR類型,但是對於不同的存儲引擎也會有一些差別:

  • MyISAM存儲引擎:建議使用固定長度的數據列代替可變長度的數據列;
  • MEMORY存儲引擎:目前都是使用的固定長度的數據行存儲,因爲不管是什麼類型都是作爲CHAR類型處理的;
  • InnoDB存儲引擎:建議使用VARCHAR類型。因爲根據它的內部存儲機制,對於固定長度和可變長度沒有太大差別,都是使用指針指向數據,因此從性能上來說CHAR不一定會比VARCHAR好,而且從空間上來考慮VARCHAR更合適。

二、TEXT 與 BLOB

        前面說的CHAR 與 VARCHAR 都是存少量字符串的,對於大塊的文本需要用到TEXT 與 BLOB 。二者的主要區別在於BLOB可以用來保存二進制數據,例如圖片;而TEXT只能保存文本數據。對兩者進行細分又可分爲TEXT、MEDIUMTEXT、LONGTEXT 和 BLOB、MEDIUMBLOB、LONGBLOB,主要的差別在於存儲的長度和字節的不同,因此根據要求選用最小的。

      1. BLOB 和 TEXT 引發的性能問題

          在執行大量刪除更新操作的時候會在數據庫表中留下很大的 “ 空洞 ” ,這些空洞會造成很多性能問題,因此需要定期使用OPTIMIZE TABLE功能對這類表進行碎片整理,清除這種 “ 空洞 ” 。下面是一個具體的例子:

一、創建一個表並往裏面重複的插入3條有些微區別的數據
mysql> create table t (id varchar(100),context text);  
Query OK, 0 rows affected (0.03 sec)

mysql> insert into t values(1,repeat('haha',100));
Query OK, 1 row affected (0.01 sec)

mysql> insert into t values(2,repeat('haha',100));
Query OK, 1 row affected (0.00 sec)

mysql> insert into t values(3,repeat('haha',100));
Query OK, 1 row affected (0.00 sec)

mysql> insert into t select * from t;
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

。。。。

mysql> insert into t select * from t;
Query OK, 196608 rows affected (7.65 sec)
Records: 196608  Duplicates: 0  Warnings: 0

二、切換到information_schema數據庫查看錶t的大小
mysql> use information_schema;
Database changed
mysql> select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from tables where table_schema='test1' and table_name='t';
+----------+
| data     |
+----------+
| 176.70MB |
+----------+
1 row in set (0.00 sec)

三、回到原數據庫刪除表t中id爲1的記錄,這個記錄佔該表的1/3
mysql> use test1;
Database changed
mysql> delete from t where id=1;
Query OK, 131072 rows affected (5.88 sec)

四、再次去到information_schema數據庫查看,發現刪除後數據大小沒有變
mysql> use information_schema;
Database changed
mysql> select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from tables where table_schema='test1' and table_name='t';
+----------+
| data     |
+----------+
| 176.70MB |
+----------+
1 row in set (0.00 sec)

五、回到test1數據庫清理碎片
mysql> use test1;
Database changed
mysql> optimize table t;
+---------+----------+----------+-------------------------------------------------------------------+
| Table   | Op       | Msg_type | Msg_text                                                          |
+---------+----------+----------+-------------------------------------------------------------------+
| test1.t | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| test1.t | optimize | status   | OK                                                                |
+---------+----------+----------+-------------------------------------------------------------------+
2 rows in set (6.14 sec)

六、再去information_schema查看大小情況,此時可以看到數據減小了,但不是標準的1/3
mysql> use information_schema;
Database changed
mysql> select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from tables where table_schema='test1' and table_name='t';
+----------+
| data     |
+----------+
| 134.66MB |
+----------+
1 row in set (0.00 sec)

      2. 使用合成索引提高大文本字段(TEXT、BLOB)的查詢性能

         合成索引就是根據大文本字段的內容來建立一個散列值,就相當於我保存的網頁連接來指代頁面的內容,這樣查找的時候不用比較大段的文本,只需要比較這個鏈接,很明顯,這極大的提高了查詢速度;但是要注意,這種方式只適合精確匹配,對模糊查詢及範圍查詢沒有用處(對比網頁鏈接的例子很容易理解,一個鏈接只對應一個頁面,並不能通過鏈接之間的關係來找多個頁面)。散列值可以用MD5()函數、SHA1()函數、CRC32()函數甚至是自己定義的函數來生成,請注意這樣生成的散列值末尾不要帶空格,因爲CHAR類型會去除尾部空格造成不匹配的影響。

下面看一個例子:

刪除之前存在的t表重新創建一個並插入數據
mysql> drop table t;
Query OK, 0 rows affected (0.01 sec)

mysql> create table t (id varchar(100),context blob,hash_value varchar(40));
Query OK, 0 rows affected (0.02 sec)

mysql> insert into t values(1,repeat('beijing',2),md5(context));
Query OK, 1 row affected (0.01 sec)

mysql> insert into t values(2,repeat('beijing',2),md5(context));
Query OK, 1 row affected (0.00 sec)

mysql> insert into t values(3,repeat('beijing 2008',2),md5(context));
Query OK, 1 row affected (0.00 sec)

mysql> select * from t;
+------+--------------------------+----------------------------------+
| id   | context                  | hash_value                       |
+------+--------------------------+----------------------------------+
| 1    | beijingbeijing           | 09746eef633dbbccb7997dfd795cff17 |
| 2    | beijingbeijing           | 09746eef633dbbccb7997dfd795cff17 |
| 3    | beijing 2008beijing 2008 | 1c0ddb82cca9ed63e1cacbddd3f74082 |
+------+--------------------------+----------------------------------+
3 rows in set (0.00 sec)

使用散列值來精準查找數據
mysql> select * from t where hash_value=md5(repeat('beijing 2008',2));
+------+--------------------------+----------------------------------+
| id   | context                  | hash_value                       |
+------+--------------------------+----------------------------------+
| 3    | beijing 2008beijing 2008 | 1c0ddb82cca9ed63e1cacbddd3f74082 |
+------+--------------------------+----------------------------------+
1 row in set (0.00 sec)

           從上面的例子可以看出散列值可以用來精準匹配,但如果非要進行模糊匹配怎麼辦?MySQL提供了前綴索引,如下例:

mysql> create index idx_blob on t(context(100));
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc select * from t where context like 'beijing%' \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t
   partitions: NULL
         type: ALL
possible_keys: idx_blob
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

三、浮點數與定點數

       浮點數類型有float、double等,如果一個字段被定義爲浮點類型且指定了寬度,那麼當數據的寬度超過時會自動四捨五入;

      定點數與浮點數不同,它實際上是以字符串形式存放的,因而沒有類型誤差,更準確。如果插入的數值精度大於定義的精度,默認模式SQLMode下會先警告再四捨五入進行插入;TRADITIONAL傳統模式下會報錯,數據無法插入。

看例子:

mysql> drop table t;
Query OK, 0 rows affected (0.02 sec)

mysql> create table t (f float(8,1),d decimal(8,1));
Query OK, 0 rows affected (0.02 sec)

mysql> desc t;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| f     | float(8,1)   | YES  |     | NULL    |       |
| d     | decimal(8,1) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> insert into t values (1.23456,1.23456);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from t;
+------+------+
| f    | d    |
+------+------+
|  1.2 |  1.2 |
+------+------+
1 row in set (0.00 sec)

mysql> insert into t values (1.25456,1.25456);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from t;
+------+------+
| f    | d    |
+------+------+
|  1.2 |  1.2 |
|  1.3 |  1.3 |
+------+------+
2 rows in set (0.00 sec)

           單精度類型的數據超過7位時會產生誤差,比如下面的例子:

mysql> create table te (c1 float(10,2),c2 decimal(10,2));
Query OK, 0 rows affected (0.02 sec)

mysql> insert into te values(131072.32,131072.32);
Query OK, 1 row affected (0.01 sec)

mysql> select * from te;
+-----------+-----------+
| c1        | c2        |
+-----------+-----------+
| 131072.31 | 131072.32 |
+-----------+-----------+
1 row in set (0.00 sec)

這就告訴我們在精度要求較高的時候儘量使用定點數來防止這種情況的發生,另外,浮點數的比較也會因精度問題產生意料之外的錯誤,比如7.22 == 7.22結果不一定成立,7.22-7.0的結果不是想象中的0.22等等,這些都是需要避免的。

四、日期類型的選擇

      MySQL裏面日期類型有DATE、TIME、DATETIME、DATESTAMP等,選用的原則基本如下:

  • 選擇能夠滿足應用的最小存儲日期類型;比如只要年份那1個字節的YEAR就夠了,沒必要用4個字節的DATE;
  • 如果記錄的年份比較久遠,那麼最好使用DATETIME而不是DATESTAMP,因爲DATESTAMP表示的日期範圍要短很多;
  • 如果記錄的日期需要讓不同時區的用戶使用,那麼要選TIMESTAMP,因爲日期類型中只有它能和時區對應。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章