Mysql性能优化

  • 从数据类型开始
  • 设计数据库时选取列数据类型的原则
  • 如何选择标志列的数据类型
  • 如果选取普通列的数据类型
  • 存储数字
  • 整数
  • 实数
  • 存储字符串
  • varchar(n) 与char(n)
  • binary(n) 和 varbinary(n)
  • BLOB与TEXT类型
  • 字符集
  • 集合类型ENUM 和 SET
  • BIT


从数据类型开始

设计数据库时选取列数据类型的原则

  • 在确保没有低估值的存储范围的前提下,尽可能使用 存储数据的最小数据类型


  • 尽可能使用简单的数据类型

  • 实例:应该使用Mysql内建的类型而不是字符串来存储日期和时间
    应该使用整型存储IP地址而不是varchar(15):因为IPv4实际是无符号整数而不是字符串,使用应该用无符号整数存储IP地址待验证
  • 尽量避免NULL:最好指定列为NOT NULL,除非真的需要存储NULL
  • 原因:MYSQL很难优化查询中的NULL列,因为NULL列使得索引、索引统计和值比较更为复杂。
    NULL列会使用更多的存储空间,
    当NULL列被索引时,每个索引记录需要一个额外的字节,在MyISAM里可能导致固定大小的索引编程可变大小索引。


如何选择标志列的数据类型

标志列可能会用作外键,一旦标志列的数据类型确定好了,就要确保所有关联表中都使用同样的类型,否则可能导致性能问题或者未知的错误

  • 整数类型
    最好的选择:因为快而且auto_increment
  • enum和set类型
    糟糕的选择
  • 字符串类型
    尽量别使用:慢而且耗空间

如果选取普通列的数据类型

存储数字

整数
  • 整数类型
    1、它们存储值的范围从-2^(N-1) ~ 2^(N-1)-1,其中N是存储空间的位数
    2、选择不同的类型决定怎么在内存和磁盘中保存数据,但是除了一些聚合函数使用decimal和double计算外,一般使用64位的bitint整数进行整数计算,即使在32位的环境中也是如此

整数类型

存储空间

范围(有符号)

范围(无符号)

TINYINT

8bit(1 字节)

(-128,127)

(0,255)

SMALLINT

16bit(2 字节)

(-32 768,32 767)

(0,65 535)

MEDIUMINT

24bit(3 字节)

(-8 388 608,8 388 607)

(0,16 777 215)

INT或INTEGER

32bit(4 字节)

(-2 147 483 648,2 147 483 647)

(0,4 294 967 295)

BIGINT

64bit(8 字节)

(-9 233 372 036 854 775 808,9 223 372 036 854 775 807)

(0,18 446 744 073 709 551 615)

  • 有符号与无符号
    1、Mysql存储整数默认是有符号的,如果想要只存储无符号整数,需要选择UNSIGNED属性。
    2、unsigned可以使得整数的上限提高一般,比如tinyint unsigned可以存储0-255之间的数组,而tinyint存储范围是-128到127。
    3、有符号和无符号类型使用相同的存储空间,并具有相同的性能,因此可以根据实际情况选择合适的类型
    4、一般情况下,如果选择int,推荐用有符号数,使用无符号数只是比原来多一倍得取值,数量级上没有改变。如果需要取值范围很大,直接选择用BIGINT
mysql> create table test_unsigned(a int unsigned, b int unsigned);

mysql> insert into test_unsigned values(1, 2);

mysql> select a - b from test_unsigned;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`burn_test`.`test_unsigned`.`a` - `burn_test`.`test_unsigned`.`b`)'

mysql> select b - a from test_unsigned;
+-------+
| b - a |
+-------+
|     1 |
+-------+

mysql> set sql_mode = 'no_unsigned_subtraction'; -- 这样就可以得到负数

mysql> select a - b from test_unsigned;
+-------+
| a - b |
+-------+
|    -1 |
+-------+
  • Mysql可以为整数类型指定宽度,比如INT(11),但是这是没有意义的,对于存储和计算来说,INT(1)和INT(20)是相同的,只是会限制可以表示的值范围。当存储时使用的存储空间依然是4字节
  • INT(N)默认是不起作用的,除非和zerofull搭配
  • 当存储的数据长度小于N,zerofull会用数字0填充左边然后显示,没有zerofull原样显示数据
  • 当存储的长度大于N,两个都是原样显示
  • 也就是说, INT(N)只是跟显示有关,不影响实际存储
mysql> create table test_int_n(a int(3) zerofill, b int(3));

mysql> insert into test_int_n(a, b) values(1, 1);

mysql> select * from test_int_n;
+------+------+
| a    | b    |
+------+------+
|  001 |    1 |
+------+------+

mysql> insert into test_int_n(a, b) values(1111, 11111);

mysql> select * from test_int_n;
+------+-------+
| a    | b     |
+------+-------+
|  001 |     1 |
| 1111 | 11111 |

mysql> select a, HEX(a) from test_int_n\G
*************************** 1. row ***************************
     a: 001
HEX(a): 1    -- 实际存储的还是1
*************************** 2. row ***************************
     a: 1111
HEX(a): 457  -- 1111对应的16进制就是457
2 rows in set (0.00 sec)
  • AUTO_INCREMENT
  • 必须是索引的一部分
  • AUTO_INCREMENT是实例启动时,取当前表的最大值,然后 +1 即为下次自增的值。(MAX + 1)
mysql> create table test_auto_increment(a int auto_increment);
ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
-- 没有指定为key,报错了

mysql> create table test_auto_increment(a int auto_increment primary key);  -- 指定为key后有效

mysql> insert into test_auto_increment values(NULL);  -- 插入NULL值

mysql> select * from test_auto_increment;
+---+
| a |
+---+
| 1 |  -- 插入NULL值,便可以让其自增,且默认从1开始
+---+

mysql> insert into test_auto_increment values(0);  -- 插入 0

mysql> select * from test_auto_increment;
+---+
| a |
+---+
| 1 |
| 2 |  -- 插入 0 ,自增长为2
+---+

mysql> insert into test_auto_increment values(-1);  -- 插入 -1

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -1 |   -- 刚刚插入的-1
|  1 |
|  2 |
+----+

mysql> insert into test_auto_increment values(NULL);  -- 继续插入NULL

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -1 |
|  1 |
|  2 |
|  3 |  -- 刚刚插入NULL, 自增为3
+----+

mysql> insert into test_auto_increment values('0'); -- 插入字符0

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -1 |
|  1 |
|  2 |
|  3 |
|  4 |  -- 插入字符'0' 后, 自增长为4
+----+

mysql> update test_auto_increment set a = 0 where a = -1;  -- 更新为0
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test_auto_increment;
+---+
| a |
+---+
| 0 |  -- 原来的 -1 更新为0
| 1 |
| 2 |
| 3 |
| 4 |
+---+

--
--  数字 0 这个值比较特殊, 插入0和插入NULL的效果是一样的,都是代表自增
--

-----

mysql> insert into test_auto_increment values(NULL), (100), (0);
Query OK, 3 rows affected (0.02 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> select * from test_auto_increment;
+-----+
| a   |
+-----+
|   0 |
|   1 |
|   2 |
|   3 |
|   4 |
|   5 | -- 第一个NULL
| 100 | -- 100
| 101 | -- 0, 按当前最大的值 +1来设置,之前是100,所以这里101
+-----+

mysql> insert into test_auto_increment values(99); -- 插入99

mysql> select * from test_auto_increment;
+-----+
| a   |
+-----+
|   0 |
|   1 |
|   2 |
|   3 |
|   4 |
|   5 |
|  99 |  -- 刚刚插入的 99
| 100 |
| 101 |
+-----+

注意: insert into tablename select NULL; 等价与 insert into tablename values (NULL);

mysql>  create table test_auto_increment(a int auto_increment primary key);

mysql> insert into test_auto_increment values(-1);

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -1 |
+----+

mysql> insert into test_auto_increment values(-2);

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -2 |
| -1 |
+----+

mysql> insert into test_auto_increment values(3);

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -2 |
| -1 |
|  3 |
+----+

mysql> insert into test_auto_increment values(0);

mysql> select * from test_auto_increment;
+----+
| a  |
+----+
| -2 |
| -1 |
|  3 |
|  4 |
+----+
mysql> select last_insert_id() --上一次自增的值
  • 总结:对于auto_increment,插入0和NULL表示自增。插入正数/负数会归到它应该去的位置。如果想要插入0,必须update … set来得到
实数

因为需要额外的空间和计算开销,所以应该尽量避免只在小数进行精确计算时才使用decimal—比如存储财务数据

实数类型

存储空间

范围(有符号)

范围(无符号)

用途

FLOAT

4 字节

(-3.402 823 466 E+38,1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38)

0,(1.175 494 351 E-38,3.402 823 466 E+38)

单精度浮点数

DOUBLE

8 字节

(1.797 693 134 862 315 7 E+308,2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)

0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308)

双精度浮点数值,精度比float高

DECIMAL

对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2

依赖于M和D的值

依赖于M和D的值

小数值[精度十分高]

哎,不纠结了,如果要使用小数的话,直接用DECIMAL就好

存储字符串

类型

说明

N的含义

是否有字符集

最大长度

CHAR(N)

定长字符

字符


255

VARCHAR(N)

变长字符

字符


16384

BINARY(N)

定长二进制字节

字节


255

VARBINARY(N)

变长二进制字节

字节


16384

TINYBLOB(N)

二进制大对象

字节


256

BLOB(N)

二进制大对象

字节


16K

MEDIUMBLOB(N)

二进制大对象

字节


16M

LONGBLOB(N)

二进制大对象

字节


4G

TINYTEXT(N)

大对象

字节


256

TEXT(N)

大对象

字节


16K

MEDIUMTEXT(N)

大对象

字节


16M

LONGTEXT(N)

大对象

字节


4G

varchar(n) 与char(n)
  • varchar:
    1、存储可变长字符串,比定长类型更加节省空间,因为它仅仅使用必要的空间
    2、varchar需要使用1或者2个额外字节记录字符串的长度:如果列的最大长度小于或者等于255字节,则只需要使用1个字节表示,否则需要两个字节
    3、什么时候使用varchar




  • char
    1、存储定长字符串:在存储时会删除所有的末尾空格,但是如果在比较时存储的字符串少于定义的长度,会用空格填充以便方便比较
    2、什么时候使用char



binary(n) 和 varbinary(n)
  • varbinary:
    类似varchar,但是它存储的是二进制字符串,存储的是字节码而不是字符
  • binary
    类似char,但是填充时使用\0填充而不是空格
mysql> select length('我'),char_length('我');
+---------------+--------------------+
| length('我')  | char_length('我')  |
+---------------+--------------------+
|             3 |                  1 |
+---------------+--------------------+

补充:字符串基本上不区分大小写,除非

mysql>create table t(a varchar(10) collate utf8mb4_bin, unique key(a));

不过这个场景用的非常少

BLOB与TEXT类型

尽量少用这两种类型
1、BLOB和TEXT都是为了存储很大的数据而设计的字符串数据类型,分别采用二进制和字符方式存储
2、它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT、SMALLTEXT、TEXT、MEDIUMTEXT和LONGTEXT;二进制家族是:TINYBLOB、SMALLBLOB、BLOB、MEDIUMBLOB和LONGBLOB。
3、BLOB家族和TEXT家族仅有的不同是:blob存储二进制数据,没有排序规则或字符集,而text有字符集和排序规则
4、MYSQL对blob和text列进行排序与其他类型是不同的:它只对每个列的最前max_sort_length字节而不是整个字符串排序。
注意:

  • 在BLOB和TEXT上创建索引时,必须指定索引前缀的长度
mysql> create table test_text(a int primary key, b text, key(b));
ERROR 1170 (42000): BLOB/TEXT column 'b' used in key specification without a key length

mysql> create table test_text(a int primary key, b text, key(b(64)));
  • BLOB和TEXT列不能有默认值
mysql> create table test_text(a int primary key, b text default 'aaa', key(b(64)));
ERROR 1101 (42000): BLOB, TEXT, GEOMETRY or JSON column 'b' can't have a default value
  • BLOB和TEXT列排序时只使用该列的前max_sort_length个字节
mysql> select @@max_sort_length;
+-------------------+
| @@max_sort_length |
+-------------------+
|              1024 |
+-------------------+

不建议在MySQL中存储大型的二进制数据,比如歌曲,视频

字符集
  • 字符集
    一组符号和编码的集合叫做字符集。常见的有:utf8、utf8mb4【推荐】、gbk、gb18030
mysql> show character set like 'gb%';  //查看指定的字符集
+---------+---------------------------------+--------------------+--------+
| Charset | Description                     | Default collation  | Maxlen |
+---------+---------------------------------+--------------------+--------+
| gb18030 | China National Standard GB18030 | gb18030_chinese_ci |      4 |
| gb2312  | GB2312 Simplified Chinese       | gb2312_chinese_ci  |      2 |
| gbk     | GBK Simplified Chinese          | gbk_chinese_ci     |      2 |
mysql> show character set;   --查看MySQL支持的字符集
+----------+---------------------------------+---------------------+--------+
| Charset  | Description                     | Default collation   | Maxlen |
+----------+---------------------------------+---------------------+--------+
| armscii8 | ARMSCII-8 Armenian              | armscii8_general_ci |      1 |
| ascii    | US ASCII                        | ascii_general_ci    |      1 |
| big5     | Big5 Traditional Chinese        | big5_chinese_ci     |      2 |
---
mysql> SHOW VARIABLES like '%charact%';  -- 查看默认支持的字符集
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
mysql>  SHOW CREATE DATABASE db_name;  -- 查看当前数据库编码:

mysql> SHOW CREATE TABLE tbl_name;   --查看表编码:

mysql> SHOW FULL COLUMNS FROM tbl_name;  -- 查看字段编码:
mysql> alter table table_name convert to character set utf8mb4  -- 修改表的字符集
  • collation:指字符串的排序规则,ci(case insensitive)结尾的排序集是大小写不敏感的,默认是不区分大小写的
mysql> select 'a' = 'A';   --select 'a' = 'A    ';
+-----------+
| 'a' = 'A' |
+-----------+
|         1 |  -- 因为大小写无关,所以返回1
+-----------+

mysql> create table test_ci (a varchar(10), key(a));

mysql> insert into test_ci values('a');
mysql> insert into test_ci values('A');

mysql> select * from test_ci where a = 'a';
+------+
| a    |
+------+
| a    |
| A    |  -- A也被我们查到了
+------+

用户名不希望区分大小写

  • 修改默认collation
mysql> set names utf8mb4 collate utf8mb4_bin;  -- 当前会话有效
Query OK, 0 rows affected (0.00 sec)

mysql> select 'a' = 'A';
+-----------+
| 'a' = 'A' |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

字符集的指定,可以在创建数据库的时候指定,也可以在创建表的时候单独指定,也可以创建列的时候进行指定

集合类型ENUM 和 SET

  • ENUM类型最多允许65536个值
  • SET类型最多允许64个值
  • 通过sql_mode参数可以用户约束检查
mysql> create table test_col (
    -> user varchar(10),
    -> sex enum('male', 'female')  -- 虽然写的是字符串,单其实存储的整型,效率还是可以的
    -> );

mysql> insert into test_col values ("tom", "male");
Query OK, 1 row affected (0.02 sec)

mysql> insert into test_col values ("tom", "xmale");  -- 不是male 和 female
Query OK, 1 row affected, 1 warning (0.03 sec)  -- 有warning

mysql> show warnings;
+---------+------+------------------------------------------+
| Level   | Code | Message                                  |
+---------+------+------------------------------------------+
| Warning | 1265 | Data truncated for column 'sex' at row 1 |
+---------+------+------------------------------------------+

mysql> show variables like 'sql_mode';
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name | Value                                                                                                                                     |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+
| sql_mode      | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+

mysql> set sql_mode='strict_trans_tables';  -- 设置为严格模式
Query OK, 0 rows affected, 2 warnings (0.00 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Warning
   Code: 3135
Message: 'NO_ZERO_DATE', 'NO_ZERO_IN_DATE' and 'ERROR_FOR_DIVISION_BY_ZERO' sql modes should be used with strict mode. They will be merged with strict mode in a future release.
*************************** 2. row ***************************
  Level: Warning
   Code: 3090
Message: Changing sql mode 'NO_AUTO_CREATE_USER' is deprecated. It will be removed in a future release.
2 rows in set (0.00 sec)

mysql> show variables like 'sql_mode';
+---------------+---------------------+
| Variable_name | Value               |
+---------------+---------------------+
| sql_mode      | STRICT_TRANS_TABLES |
+---------------+---------------------+

mysql> insert into test_col values ("tom", "xmale");
ERROR 1265 (01000): Data truncated for column 'sex' at row 1
  • 强烈建议新业务上都设置成严格模式
  • enum和set适合存储固定信息,比如人的性别,产品状态
  • enum列允许在列中存储一组定义值中的单个列,set列允许在列中存储一组定义值中的一个或者多个值