表设计中的陷阱与建议

1. 数字类型

1.1 整型类型

数量字段用 unsigned 还是signed? 一般来讲,数量不可能为负值,用unsigned是理所应当的事情。但是如下情况,做此类减法的统计,那么报错就来了。大意是计算值为负,超出了unsigned的范围。这个可以通过SET sql_mode='NO_UNSIGNED_SUBTRACTION';解决。但是如果有符号整型可以满足需求,那么就不要使用无符号整型,否则就会有类似不必要的麻烦。

MariaDB [mysql]> CREATE TABLE Item 
    -> (
    -> id int NOT NULL,
    -> count int unsigned default 0,
    -> want_count int unsigned default 0
    -> );
Query OK, 0 rows affected (0.06 sec)

MariaDB [mysql]> Insert into Item (id, count, want_count) values (1, 100000, 5000);
Query OK, 1 row affected (0.01 sec)

MariaDB [mysql]> select want_count - count from Item;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(`mysql`.`Item`.`want_count` - `mysql`.`Item`.`count`)'
MariaDB [mysql]> SET sql_mode='NO_UNSIGNED_SUBTRACTION';
Query OK, 0 rows affected (0.00 sec)

MariaDB [mysql]> select want_count - count from Item;
+--------------------+
| want_count - count |
+--------------------+
|             -95000 |
+--------------------+
1 row in set (0.01 sec)

1.2 浮点类型和高精度型

float和double都是浮点类型,后者精度比前者高。但是这两个时非标准的,精度计算可能存在误差。所以较新版的mysql8.0 已经开始建议废弃。可以使用DECIMAL替代,DECIMAL(9,3) 9 是精度(整数位数),3 是标度(小数点后的位数)。用户的工资、账户的余额等精确到小数点后 2 位的业务都可以使用此类型。如果数据量大,且精度要求更高,可以使用BIGINT替代,根据需求乘以倍数即可。比如,精确到万分之一,则把所有金额乘以1万存到BIGINT中。这样同样可以解决浮点和高精度类型运算消耗资源高的问题,以及存储不定长带来的问题。

1.3 主键自增

一般来讲,整型配合auto_increment可以实现主键自增。不过要注意到int上线42亿左右,如果业务频繁这个很容易达到。所以,尽量采用BIGINT做自增主键,而不是INT。另外,MySQL 8.0 之前,自增不持久化,如果遇到数据库重启,自增值可能会存在回溯问题!

如果是海量业务,尤其是涉及到分布式架构,用 UUID 做主键或业务自定义生成主键是更好的选择。

mysql> select UUID();
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| 7ad9b7d2-b499-11eb-8166-5254001a19ec |
+--------------------------------------+
1 row in set (0.00 sec)

mysql> select UUID();
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| 7b61c72b-b499-11eb-8166-5254001a19ec |
+--------------------------------------+
1 row in set (0.00 sec)

UUID

UUID = 时间低(4字节)- 时间中高+版本(4字节)- 时钟序列 - MAC地址

2. 字符串类型

2.1 特殊字段要添加约束

比如性别只能是男女,余额必须大于0等。 如果不添加约束,那么表中的数据可能变脏导致语义不清

 CREATE TABLE Student(
     id INT PRIMARY KEY, 
     name VARCHAR(20) NOT NULL UNIQUE,
     sex CHAR(1) CHECK(sex='F' OR sex='M'),
     age INT DEFAULT 18
     );

mysql8.0之后支持check,使用ENUM 也可以,只不过ENUM 只支持mysql。

mysql> desc Student_e;
+-------+---------------+------+-----+---------+-------+
| Field | Type          | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| id    | int(11)       | NO   | PRI | NULL    |       |
| name  | varchar(20)   | NO   | UNI | NULL    |       |
| sex   | enum('M','F') | YES  |     | NULL    |       |
| age   | int(11)       | YES  |     | 18      |       |
+-------+---------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> Insert into Student_e(id,name,sex,age) values(1,"xiaoming","T",20);
ERROR 1265 (01000): Data truncated for column 'sex' at row 1

2.2 密码不要直接存储

一般情况,数据库中存放用户密码的hash值,直接存放明文,很多情况下是不安全不合规的。为了更高的安全性,可以加盐(动态盐最好了)和多种可选算法。

 

3. 日期类型

存储日期类型的字段有三种方案,DATETIME、TIMESTAMP、INT

TIMESTAMP最大数值能用到2038年,这也快到了,不建议用。INT存储时间,对数据库维护人员来说不那么友好。 而DATETIME的精度可以做到更高,而且不用考虑上限问题。

mysql> SELECT NOW(6);
+----------------------------+
| NOW(6)                     |
+----------------------------+
| 2021-05-14 16:33:00.643074 |
+----------------------------+
1 row in set (0.00 sec)

mysql> SELECT NOW(4);
+--------------------------+
| NOW(4)                   |
+--------------------------+
| 2021-05-14 16:34:16.6732 |
+--------------------------+
1 row in set (0.00 sec)

mysql> SELECT NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2021-05-14 16:34:21 |
+---------------------+
1 row in set (0.00 sec)

另外,对更新操作,表中可加一个更新时间,方便维护。

    last_modify_date DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),

4. 非结构存储

mysql8.0 支持json类型,这将会极大的方便开发。一些固定的字段,我们可在表中单独添加一列,不固定的信息则可以放进json中。这要比多个一些冗余字段要好的多,也比放到varchar中要便捷,省去来回解析json了。不过json一般是变长的,变长的就容易引起内存碎片。

DROP TABLE IF EXISTS User;

CREATE TABLE User (
    id BIGINT NOT NULL,
    loginInfo JSON,
    PRIMARY KEY(id)
);

然后插入数据

mysql> SET @info = '
    '> {
    '> "autograph" : "no pain,no gain",
    '> "wx" : "coder"
    '> }
    '> ';
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO User VALUES (1024,@info);
Query OK, 1 row affected (0.02 sec)

mysql> select * from User;
+------+-------------------------------------------------+
| id   | loginInfo                                       |
+------+-------------------------------------------------+
| 1024 | {"wx": "coder", "autograph": "no pain,no gain"} |
+------+-------------------------------------------------+
1 row in set (0.00 sec)

搜索

mysql> SELECT
    ->     id,
    ->     JSON_UNQUOTE(JSON_EXTRACT(loginInfo,"$.phone")) phone,
    ->     JSON_UNQUOTE(JSON_EXTRACT(loginInfo,"$.wx")) wx
    -> FROM User;
+------+-------+-------+
| id   | phone | wx    |
+------+-------+-------+
| 1024 | NULL  | coder |
+------+-------+-------+
1 row in set (0.00 sec)

5. 表压缩提高吞吐

在innodb存储引擎中数据是按照表空间来组织存储的,表空间文件是文件系统的物理文件。表空间是由一个个页组成的,一页默认为16K,这些也用来存储表中一行行的数据。操作系统读取数据是页为单位的,如果一页中包含的表记录越多,那么吞吐量就越大。而进行表压缩之后,相比未压缩,增加的记录就越多。

数据库可以根据记录、页、表空间进行压缩,实际用的比较多的是页压缩。

如果业务系统,CPU富余,但IO负载更不上,可以用压缩提高IO吞吐。

CREATE TABLE Stu (
    id BIGINT NOT NULL,
    loginInfo JSON,
    PRIMARY KEY(id)
)
ROW_FORMAT=COMPRESSED
KEY_BLOCK_SIZE=8;

创建表时ROW_FROMAT 设为 COMPRESS,将启用页压缩功能,KEY_BLOCK_SIZE 设置为 8,将会把一个 16K 的页压缩为 8K。不过一个压缩页在内存缓冲池中,存在压缩和解压两个页,开销也比较大。

表压缩主要用于字符类型比较大的表上(VARCHAR,VARBINARY和BLOB和TEXT类型),读操作远远多于写操作。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值