数据库大数据学习

MySQL进阶:表结构设计

2021-05-30  本文已影响0人  xiaogp

摘要:MySQL

表结构设计是MySQL架构设计最为基础的工作,优化表结构设计是开发规范,避免后期因为表结构设计不合理导致的付出巨大调整代价


数字类型

对于数字类型的表结构设计总结

(1)整数类型

MySQL 数据库支持 SQL 标准支持的整型类型:INT、SMALLINT。此外,MySQL 数据库也支持诸如 TINYINT、MEDIUMINT 和 BIGINT 整型类型(表 1 显示了各种整型所占用的存储空间及取值范围)

在整型类型中,有 signedunsigned 属性,其表示的是整型的取值范围,默认为 signed

两者的取值的范围极差是一致的,signed相当于把unsigned截取了一半放到了负数上,对于像年龄,成绩等必定为非负数的数值可以设置为unsigned,支持零和正整数,例如

mysql> CREATE TABLE `student` (
    ->   id INT AUTO_INCREMENT PRIMARY KEY,
    ->   age INT unsigned DEFAULT NULL
    -> ) ;
Query OK, 0 rows affected (0.86 sec)

插入非负数正确,插入-1报错

mysql> INSERT `student` (`age`) VALUES (0);
Query OK, 1 row affected (0.08 sec)
mysql> INSERT `student` (`age`) VALUES (-1);
ERROR 1264 (22003): Out of range value for column 'age' at row 1

但是使用unsigned之后会出现一个问题,即当需要对unsigned进行数据分析计算时,如果计算结果存在负数则会报错out of range,即MySQL 要求 unsigned 数值相减之后依然为 unsigned,否则就会报错

mysql> select id, age - 10 from student;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`test`.`student`.`age` - 10)'

如果计算结果不违背非负数则能正常运行

mysql> select id, age +10 from student;
+----+---------+
| id | age +10 |
+----+---------+
|  1 |      10 |
|  2 |      60 |
+----+---------+

为了避免这个错误,需要对数据库参数 sql_mode 设置为NO_UNSIGNED_SUBTRACTION,允许相减的结果为 signed,这样才能得到最终想要的结果

mysql> SET sql_mode='NO_UNSIGNED_SUBTRACTION';
Query OK, 0 rows affected (0.00 sec)

mysql> select id, age - 10 from student;
+----+----------+
| id | age - 10 |
+----+----------+
|  1 |      -10 |
|  2 |       40 |
+----+----------+

在开发过程中不建议刻意使用unsigned,因为他降低了容错率,在进行插入和分析时限制为非负数,可能导致输出结果报错


(2)浮点类型和高精度型

MySQL 之前的版本中存在浮点类型Float 和Double,但这些类型因为不是高精度,也不是 SQL 标准的类型,所以在真实的生产环境中不推荐使用,否则在计算时,由于精度类型问题,会导致最终的计算结果出错。 MySQL 8.0.17 版本开始,当创建表用到类型 Float 或 Double 时,会抛出下面的警告:MySQL 提醒用户不该用上述浮点类型,甚至提醒将在之后版本中废弃浮点类型,而数字类型中的高精度 DECIMAL 类型可以使用,当声明该类型列时,可以(并且通常必须要)指定精度标度,比如

mysql> CREATE TABLE `student` (
    ->   id INT AUTO_INCREMENT PRIMARY KEY,
    ->   salary DECIMAL(8, 2) DEFAULT NULL
    -> ) ;
Query OK, 0 rows affected (0.24 sec)

插入数据查看结果,可见MySQL将小数保留到decimal指定的标度,保留小数点后2位,精度表示所有数值的最大个数,如果精度为8,标度为2,则小数点前的数字个数为最大6个,小数点后面的数字如果超过2个则自动截断,即存储的小数范围是-999999.99到999999.99

mysql> INSERT `student` (`salary`) VALUES (1);
Query OK, 1 row affected (0.03 sec)

mysql> INSERT `student` (`salary`) VALUES (1.2345);
Query OK, 1 row affected, 1 warning (0.03 sec)

mysql> INSERT `student` (`salary`) VALUES (123456789.2345);
ERROR 1264 (22003): Out of range value for column 'salary' at row 1
mysql> INSERT `student` (`salary`) VALUES (123456.23);
Query OK, 1 row affected (0.06 sec)
mysql> INSERT `student` (`salary`) VALUES (-3);
Query OK, 1 row affected (0.04 sec)

mysql> select * from student;
+----+-----------+
| id | salary    |
+----+-----------+
|  1 |      1.00 |
|  2 |      1.23 |
|  3 | 123456.23 |
|  4 |     -3.00 |
+----+-----------+
4 rows in set (0.00 sec)

DECIMAL通常用于保留准确精确度的列,例如会计系统中的货币数据


(3)整型类型与自增设计

整型类型的另一个常见且重要的使用用法是作为表的主键,即用来唯一标识一行数据。整型结合属性 auto_increment,可以实现自增功能,其中需要注意

INT 的范围最大在 42 亿的级别(如果设置成unsigned),在真实的互联网业务场景的应用中,很容易达到最大值。例如一些流水表、日志表,每天 1000W 数据量,420 天后,INT 类型的上限即可达到。因此用自增整型做主键,一律使用 BIGINT,而不是 INT。不要为了节省 4 个字节使用 INT,对INT自增做测试如下

mysql> CREATE TABLE `student` (
    ->   id INT AUTO_INCREMENT PRIMARY KEY,
    ->   name VARCHAR(100) DEFAULT NULL
    -> );
Query OK, 0 rows affected (0.25 sec)

插入INT最大自增值,此时再插入一条数据报错主键重复输入

mysql> INSERT `student` (name) VALUES ('xgp');
Query OK, 1 row affected (0.03 sec)

mysql> INSERT `student` (id, name) VALUES (2147483647, 'gp');
Query OK, 1 row affected (0.04 sec)

mysql> INSERT `student` (name) VALUES ('wf');
ERROR 1062 (23000): Duplicate entry '2147483647' for key 'PRIMARY'

默认的INT AUTO_INCREMENT是从1开始的,并且是signed模式,最大值是2147483647

mysql> select * from student;l
+------------+------+
| id         | name |
+------------+------+
|          1 | xgp  |
| 2147483647 | gp   |
+------------+------+

可以将AUTO_INCREMENT和UNSIGNED连用,这样可以扩大取值范围,默认还是从1开始,初始值是AUTO_INCREMENT自身的一个参数

mysql> CREATE TABLE `student` (
    ->   id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ->   name VARCHAR(100) DEFAULT NULL
    -> );
Query OK, 0 rows affected (0.37 sec)

mysql> INSERT `student` (name) VALUES ('xgp');
Query OK, 1 row affected (0.03 sec)

mysql> INSERT `student` (id, name) VALUES (2147483647, 'gp');
Query OK, 1 row affected (0.03 sec)

mysql> INSERT `student` (name) VALUES ('wf');
Query OK, 1 row affected (0.04 sec)

mysql> select * from student;
+------------+------+
| id         | name |
+------------+------+
|          1 | xgp  |
| 2147483647 | gp   |
| 2147483648 | wf   |
+------------+------+

UNSIGNED的INT最大值是4294967295,超过了报错主键重复插入

mysql> INSERT `student` (id, name) VALUES (4294967295, 'gp2');
Query OK, 1 row affected (0.05 sec)

mysql> INSERT `student` (name) VALUES ('wf2');
ERROR 1062 (23000): Duplicate entry '4294967295' for key 'PRIMARY'

正确的使用BIGINT + UNSIGNED做自增主键的例子如下,最大值是18446744兆亿

CREATE TABLE `student` (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) DEFAULT NULL
);

MySQL 8.0 版本前,自增不持久化,自增值可能会存在回溯问题,是指MySQL记录的自增值不会持久化到磁盘,因此当发生宕机时,自增值可能回到之前的值,测试如下

mysql> CREATE TABLE `student` (
    ->   id INT AUTO_INCREMENT PRIMARY KEY,
    ->   salary DECIMAL(8, 2) DEFAULT NULL
    -> ) ;
Query OK, 0 rows affected (0.24 sec)

再插入数据,同时使用create table的AUTO_INCREMENT查看下一个数据的自增主键值

mysql> INSERT `student` (`salary`) VALUES (1);
Query OK, 1 row affected (0.05 sec)

mysql> INSERT `student` (`salary`) VALUES (1.2345);
Query OK, 1 row affected, 1 warning (0.03 sec)

mysql> INSERT `student` (`salary`) VALUES (123456.23);
Query OK, 1 row affected (0.04 sec)

mysql> show create table `student`;
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                      |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| student | CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `salary` decimal(8,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select * from student;
+----+-----------+
| id | salary    |
+----+-----------+
|  1 |      1.00 |
|  2 |      1.23 |
|  3 | 123456.23 |
+----+-----------+
3 rows in set (0.00 sec)

当下已插入3条数据,下一个数据的自增值是4,此时删除第3条数据,自增值还是4

mysql> delete from student where id = 3;
Query OK, 1 row affected (0.09 sec)

mysql> show create table `student`;
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                      |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| student | CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `salary` decimal(8,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

此时如果从后台kill掉MySQL进程,再次打开MySQL查看表的自增值,重新变为3

mysql> show create table student;
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                      |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| student | CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `salary` decimal(8,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

若要彻底解决这个问题,需要升级 MySQL 版本到 8.0 版本,每张表的自增值会持久化,否则强烈不推荐在核心业务表中使用自增数据类型做主键,在海量互联网架构设计过程中,为了之后更好的分布式架构扩展性,不建议使用整型类型做主键,更为推荐的是字符串类型


(4)资金字段设计

在海量互联网业务的设计标准中,并不推荐用 DECIMAL 类型,而是更推荐将 DECIMAL 转化为 整型类型。也就是说,资金类型更推荐使用用分单位存储,而不是用元单位存储。如1元在数据库中用整型类型 100 存储。原因如下

推荐使用 BIG INT 来存储金额相关的字段。字段存储时采用分存储,即便这样 BIG INT 也能存储千兆级别的金额。这样的好处是,所有金额相关字段都是定长字段,占用 8 个字节,存储高效。另一点,直接通过整型计算,效率更高。在数据库设计中,定长存储的性能更好


字符串类型

对于字符串类型字段的表结构设计总结


(1)CHAR 和 VARCHAR 的定义

MySQL 数据库的字符串类型有 CHAR、VARCHAR、BINARY、BLOB、TEXT、ENUM、SET。不同的类型在业务设计、数据库性能方面的表现完全不同,其中最常使用的是 CHARVARCHAR

CHAR和VARCHAR的N都是字符数量,如

mysql> CREATE TABLE `student` (
    ->   id INT AUTO_INCREMENT PRIMARY KEY,
    ->   name VARCHAR(4)  DEFAULT NULL,
    ->   phone CHAR(11) DEFAULT NULL
    -> ) ;
Query OK, 0 rows affected (0.25 sec)

插入数据测试,可见不论中文字符串还是英文字符串,都是算一个字符数,超过N则报错too long

mysql> INSERT `student` (`name`, `phone`) VALUES ('wbcv', '13852517263');
Query OK, 1 row affected (1.08 sec)

mysql> INSERT `student` (`name`, `phone`) VALUES ('我是什么', '13852517262');
Query OK, 1 row affected (0.16 sec)

mysql> INSERT `student` (`name`, `phone`) VALUES ('我是什么吗', '13852517265');
ERROR 1406 (22001): Data too long for column 'name' at row 1
mysql> INSERT `student` (`name`, `phone`) VALUES ('我是什么', '1385251726');
Query OK, 1 row affected (0.53 sec)

mysql> INSERT `student` (`name`, `phone`) VALUES ('wbcvx', '13852517263');
ERROR 1406 (22001): Data too long for column 'name' at row 1

MySQL 数据库的 VARCHAR 字符类型,最大能够存储 65536 个字符,所以在 MySQL 数据库下,绝大部分场景使用类型 VARCHAR 就足够了


(2)字符集

MySQL中有utf8和utf8mb4两种编码,在MySQL中推荐永远使用utf8mb4而不是utf8,这是mysql的一个遗留问题,MySQL中的utf8最多只能支持3bytes长度的字符编码,对于一些需要占据4bytes的文字,mysql的utf8就不支持了,要使用utf8mb4才行,包括 MySQL 8.0 版本在内,字符集默认设置成 UTF8MB4,8.0 版本之前默认的字符集为 Latin1
可以在建表时在最后加上表的字符集

mysql> CREATE TABLE `student` (
    ->   id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ->   name VARCHAR(100) DEFAULT NULL
    -> ) charset=utf8mb4;
Query OK, 0 rows affected (0.27 sec)

也可以在创建数据库的时候就指定库下所有表的默认字符集

mysql> create database test2 default charset utf8mb4;
Query OK, 1 row affected (0.00 sec)

对于已经指定字符集的表,可以通过CONVERT TO CHARSET语句修改

mysql> ALTER TABLE student CONVERT TO CHARSET utf8mb4;
Query OK, 0 rows affected (0.70 sec)
(3)排序规则

每个字符集都有对应的排序规则(Collation),utf8mb4默认对应的排序规则是utf8mb4_general_ci

mysql> SHOW CHARSET LIKE 'utf8%';
+---------+---------------+--------------------+--------+
| Charset | Description   | Default collation  | Maxlen |
+---------+---------------+--------------------+--------+
| utf8    | UTF-8 Unicode | utf8_general_ci    |      3 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci |      4 |
+---------+---------------+--------------------+--------+

排序规则以 _ci 结尾,表示不区分大小写(Case Insentive),_cs 表示大小写敏感,_bin 表示通过存储字符的二进制进行比较。需要注意的是,比较 MySQL 字符串,默认采用不区分大小的排序规则MySQL在做大小比较,排序,去重的时候对于字符串的情况不考虑大小写

mysql> select * from student where name = 'a';
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | A    |
+----+------+

绝大部分业务的表结构设计无须设置排序规则为大小写敏感

(4)使用枚举类型处理固定选项值的字段

对于取值是固定选项的字段,比如性别,可以采用数值类型比如tinyint但这样设计问题比较明显

推荐使用ENUM字符串枚举类型,如果插入值不在ENUM的范围则会报错截断该条记录全部删除

mysql> CREATE TABLE `student` (
    ->   `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ->   `sex` ENUM('M', 'F'),
    ->   `name` VARCHAR(100)
    -> );
mysql> INSERT INTO student (sex, name) VALUES ('F', 'gp');
Query OK, 1 row affected (0.04 sec)

mysql> INSERT INTO student (sex, name) VALUES ('M', 'wf');
Query OK, 1 row affected (0.04 sec)

mysql> INSERT INTO student (sex, name) VALUES ('G', 'slj');
ERROR 1265 (01000): Data truncated for column 'sex' at row 1
mysql> select * from student;
+----+------+------+
| id | sex  | name |
+----+------+------+
|  1 | F    | gp   |
|  2 | M    | wf   |
+----+------+------+

参考

姜承尧的MySQL实战宝典 https://kaiwu.lagou.com/course/courseInfo.htm?courseId=869#/content
MYSQL中的COLLATE是什么? https://www.jianshu.com/p/f8707b8461d3

上一篇下一篇

猜你喜欢

热点阅读