高性能MySQL实战(一):表结构

大家好,我是 方圆。最近因需求改动新增了一些数据库表,但是在定义表结构时,具体列属性的选择有些不知其所以然,索引的添加也有遗漏和不规范的地方,所以我打算为创建一个高性能表的过程以实战的形式写一个专题,以此来学习和巩固这些知识。原文还是收录在我的 Github: enthusiasm 中,欢迎Star和获取原文。

1. 实战

我使用的 MySQL 版本是 5.7,建表 DDL 语句如下所示:根据需求创建 接口调用日志 数据库表,请大家浏览具体字段的属性信息,它们有不少能够优化的点。

CREATE TABLE `service_log` (`id` bigint(100) NOT NULL AUTO_INCREMENT COMMENT '主键',`service_type` int(10) DEFAULT NULL COMMENT '接口类型',`service_name` varchar(30) DEFAULT NULL COMMENT '接口名称',`service_method` varchar(10) DEFAULT NULL COMMENT '接口方式',`serial_no` int(10) DEFAULT NULL COMMENT '消息序号',`service_caller` varchar(15) DEFAULT NULL COMMENT '调用方',`service_receiver` varchar(15) DEFAULT NULL COMMENT '接收方',`status` int(3) DEFAULT '10' COMMENT '状态 10-成功 20-异常',`error_message` varchar(200) DEFAULT NULL COMMENT '异常信息',`message` text DEFAULT NULL COMMENT '报文内容',`create_user` varchar(50) DEFAULT NULL COMMENT '创建者',`create_time` datetime NOT NULL COMMENT '创建时间',`update_user` varchar(50) DEFAULT NULL COMMENT '更新者',`update_time` datetime NOT NULL COMMENT '更新时间',`is_delete` tinyint(1) NOT NULL DEFAULT '0' COMMENT '刪除标志',`ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '时间戳',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接口调用日志';

我会在下文中将其中包含的问题和可以进行优化的地方一一进行解释,主要参考的书目是《高性能MySQL 第四版》,也希望大家有精力去看原书。

2. 优化和改进

慷慨不是明智的

一般来说,要尽量使用能够正确存储和表示数据的最小数据类型,更小的数据类型通常更快,因为它们占用的磁盘、内存和CPU缓存的空间更少,并且处理时需要的CPU周期也更少。但是,这也要确保没有低估需要存储的值的范围,否则会因入库失败而造成数据丢失,而且表结构修改的流程审批也很麻烦。

我们以表中 idmessage 列为例来说:

id 为主键列,它使用的是整数类型 BIGINT(64位),除此之外还有 TINYINT(8位)、SMALLINT(16位)、MEDIUMINT(24位) 和 INT(32位),可以存储的取值范围是从 -2(N - 1) 到 2(N - 1) - 1,所以 BIGINT 类型值的最大值是9223372036854775808(19位数)。

显然,主键定义100位宽度是有些“无脑的”,而且也是没有意义的:因为 它不会限制值的合法范围,即使是定义了 BIGINT(100) 也没办法存储宽度为100的数字,实际上定义 BIGINT(1) 和 BIGINT(20) 的 存储空间是相同的,宽度的定义只是规定了 MySQL 的一些交互工具(MySQL命令行客户端)用来显示字符的个数。

整数类型有可选的 UNSIGNED 属性,它表示不允许负值,这大约能使正整数的上限提高一倍。例如 TINYINT UNSIGNED 可以存储的值范围是 0 ~ 255,而 TINYINT 的值的存储范围是 -128 ~ 127。我们的ID列是从0开始递增的,所以可以选用这个属性。

那么,我们应该对 id 列的定义如下所示:

`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键'

message 列保存的是接口交互报文内容,定义的类型是 TEXT,它还有一些相关的类型,具体如下(L代表字符串的字节长度,数字表示存储字符串字节长度的字节数):

Data TypeStorage Required(Bytes)
TINYTEXTL + 1, L < 28
TEXTL + 2, L < 216
MEDIUMTEXTL + 3, L < 224
LONGTEXTL + 4, L < 232

若报文内容中每个字符只占用1字节的话,那么 TEXT 类型能最多存储大约 65535 个字符,而实际上报文内容远远达不到这个长度,而且 TEXT 类型是为了存储很大的数据而设计的字符串数据类型。

我们可以将其调整成 VARCHAR 类型,并根据实际的报文长度都不超过 1000 来指定它的字符数为 1000,避免发生因报文长度过长而无法保存数据的情况。通常情况下MySQL会在内容分配固定大小的内存来保存值,我们这样做节省了存储空间,对性能也有帮助。

message 的更改后的定义如下所示:

`message` varchar(1000) DEFAULT NULL COMMENT '报文内容'

VARCHAR 类型也需要额外使用 1 或 2 字节来记录字符串字节的长度:如果列的最大长度小于或等于 255 字节,则只使用 1 字节来表示;否则使用 2 字节来表示。

MySQL 字符串长度定义的不是字节数,而是字符数。像 UTF-8 这样复杂的字符集可能需要多个字节来存储一个字符。

更小的通常更好

MySQL 总是为 CHAR 类型分配所定义长度的空间,所以它是固定长度的,它相比于 VARCHAR 在面对经常修改的数据时表现更好,因为固定长度的列不容易出现内存碎片,而且对于 CHAR(1) 这种非常短的列,它要比 VARCHAR(1) 更高效,因为前者只占用 1 个字节的空间,后者占用 2 个字节(其中 1 字节记录长度)。

CHAR 类型适合存储非常短的字符串或者所有值长度都几乎相同的字符串,不过需要注意的是,MySQL 会将所有 尾随的空格移除

service_method 字段实际上保存的是接口协议,无非是 HTTP 和 TCP 这两种,我们可以将其定义修改为如下所示:

`service_method` char(4) DEFAULT NULL COMMENT '接口方式'

但是实际上,整型数据比字符数据的比较操作代价更低,如果在允许改变字段类型的情况下,我们将其修改为 TINYINT 类型,通过定义枚举值来表示不同的协议效率会更高。

`service_method` tinyint DEFAULT NULL COMMENT '接口方式 1-HTTP 2-TCP'

service_callerservice_receiver 字段也是一样的道理,这些值都是固定的枚举,最初应该也定义成 TINYINT 的形式,如下

`service_caller` tinyint DEFAULT NULL COMMENT '调用方',
`service_receiver` tinyint DEFAULT NULL COMMENT '接收方'

service_type 字段中存储的是对应接口的编码值,它们都是宽度为 4 的整型数据,最大值不会超过 9999,所以根据它的取值范围将其修改为 SMALLINT 类型会更合适,如下

`service_type` smallint DEFAULT NULL COMMENT '接口类型'

service_name 字段接口名称最长也不会超过15个字符,所以我们将它的 VARCHAR 定义字符长度修改一下:

`service_name` varchar(15) DEFAULT NULL COMMENT '接口名称'

status 字段只有 10 和 20 两种值,相比于 INT,使用 TINYINT 更合适一些

`status` tinyint DEFAULT 10 COMMENT '状态 10-成功 20-异常'

DATETIME 和 TIMESTAMP

这两种类型非常相似,对于大多数系统来说,这两种类型都可以,不过它们也有所不同。

DATETIME 可以保存的日期范围更大,从 1000 年到 9999 年,精度为 1 微秒,非小数部分 占用 5 个字节的存储空间,小数部分根据精度大小占用 0 ~ 3 个字节,并且它 与时区无关。默认情况下,MySQL 以 yyyy-MM-dd HH:mm:ss 的格式显示时间,如果需要指定精度,可以以 datetime(6) 的形式定义。

TIMESTAMP 类型存储的是自 1970 年 1 月 1 日格林尼治标准时间以来的秒数(精度也为 1 微秒),非小数部分占用 4 个字节的存储空间,小数部分与 DATETIME 类型占用空间规则一致,所以它的取值范围相比于 DATETIME 要小,只能表示从 1970 年到 2038 年 1 月 19 日的时间范围。而且该类型与MySQL服务指定的 时区相关,这就使得在查询日期时,会将时间戳转换为所在时区的时间后再显示,所以不同地区看到的同一时间戳的实际时间展示是不一样的。

MySQL 可以使用 FROM_UNIXTIME() 函数将 UNIX 时间戳转换成日期,使用 UNIX_TIMESTAMP() 函数将日期转换为 UNIX 时间戳。

使用 DATETIME 类型还是使用 TIMESTAMP 类型需要考虑以下问题:

  • 存储空间对我们来说重要吗?

  • 需要支持前后多大时间范围的日期和时间?

  • 保存的日期数据有精度要求吗?

  • 是在MySQL中处理时区还是在代码中处理时区?

拿我们的应用来说,DATETIME 类型会更合适一些:

`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`ts` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '时间戳'

如果想要对时间戳进行记录,可以考虑使用 BIGINT 类型,它不会遇到 2038 年的问题。

避免使用 NULL

通常情况下,最好指定列为 NOT NULL,除非明确的需要存储为 NULL 值。可为 NULL 的列会使用更多的存储空间,在 MySQL 中需要特殊的处理;查询中包含可为 NULL 的列对 MySQL 来说更难优化,因为可为 NULL 的列使得索引、索引统计和值的比较更为复杂。

MySQL 默认的行格式为 DYNAMIC,它会在每行数据中记录额外信息,其中就包括对 NULL 值列表的记录,如果我们所有的列都为 NOT NULL 的话,那么这部分额外信息是不需要记录的。

了解:COMPRESSED 行格式与 DYNAMIC 不同的是,它会对存储数据的页进行压缩以节省空间;COMPACT 行格式与 DYNAMIC 和 COMPRESSED 不同的是在对溢出列的处理上,COMPACT 会存储溢出列的部分数据,剩余的数据使用其他数据页保存,并记录下保存这些数据页的指针,DYNAMIC 和 COMPRESSED 则是将该列所有数据都保存在其他数据页中,在该列数据处只保存对应溢出页的地址。

COMPACT行格式示意图.png

但是实际上将列的定义修改为 NOT NULL 带来的性能提升并不明显,所以并不会将这种优化作为首选,而是在表结构初始化时考虑到这一点。

修改好,最终初始化表结构的 DDL 语句如下:

CREATE TABLE `service_log` (`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',`service_type` smallint NOT NULL DEFAULT -1 COMMENT '接口类型',`service_name` varchar(30) DEFAULT '' COMMENT '接口名称',`service_method` tinyint NOT NULL DEFAULT -1 COMMENT '接口方式 1-HTTP 2-TCP',`serial_no` int DEFAULT -1 COMMENT '消息序号',`service_caller` tinyint DEFAULT -1 COMMENT '调用方',`service_receiver` tinyint DEFAULT -1 COMMENT '接收方',`status` tinyint DEFAULT 10 COMMENT '状态 10-成功 20-异常',`error_message` varchar(200) DEFAULT '' COMMENT '异常信息',`message` varchar(1000) DEFAULT '' COMMENT '报文内容',`create_user` varchar(50) DEFAULT '' COMMENT '创建者',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_user` varchar(50) DEFAULT '' COMMENT '更新者',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',`is_delete` tinyint NOT NULL DEFAULT 0 COMMENT '刪除标志',`ts` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '时间戳',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='接口调用日志';

TINYINT 表示 Boolean 类型

需要注意,Boolean 类型的值在 MySQL 中是通过 TINYINT 来映射的,如果在数据库中该值为 0,那么映射到 Java 对象中为 False,如下所示:

tyint.png


实数类型

实数类型因为在该表结构中使用不到我们没有介绍,所以在这里进行补充。

MySQL 既支持 精确计算 的类型(DECIMAL),也支持 近似计算 的浮点类型(FLOAT 和 DOUBLE)。

FLOAT 使用 4 个字节的存储空间,DOUBLE 使用 8 个字节的存储空间,可以指定列的精度,但是通常情况下建议 只指定数据类型,而不指定精度,否则 MySQL 会根据精度自行进行舍入,而且它们还会受到平台或实现依赖性的影响。

我们看下边这个例子:

CREATE TABLE `real_number` (`f1` float(7, 4) NOT NULL,`f2` float NOT NULL,`d1` double(7, 4) NOT NULL,`d2` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='实数';# 插入数据
INSERT into real_number values (3.1415926535,3.1415926535,3.1415926535,3.1415926535
);# 查询结果
select * from real_number;
f1f2d1d2
3.14163.141593.14163.1415926535

根据结果值我们可以发现,指定了精度的浮点类型进行了舍入,没有指定精度的 FLOAT 类型默认保留了小数点后 5 位小数,自行的舍入可能会引起混淆。

通常情况下,我们为了保证最大限度的实现 可移植性,需要存储近似数字数据值的代码应该使用 FLOAT 或 DOUBLE,而不指定精度或位数。

还有一种情况需要注意,如果我们要插入超过指定精度的整数范围,会导致数据入库失败,如下:

# 指定 f1 列整数宽度为 4,实际定义允许的最大宽度为 3
INSERT into real_number values (
3210.1415926535,
3.1415926535,
3.1415926535,
3.1415926535
);# 结果
SQL 错误 [1264] [22001]: Data truncation: Out of range value for column 'f1' at row 1

如果没有指定精度范围,那么则会对小数部分进行压缩,精度变小,而不是提示入库失败,如下:

# f2 列插入该值,查看结果
INSERT into real_number values (
3.1415926535,
3210.1415926535,
3.1415926535,
3.1415926535
);
f1f2d1d2
3.14163210.143.14163.1415926535

DECIMAL 与 FLOAT 和 DOUBLE 不同,在进行精确的小数计算时,需要指定它的精度,否则默认情况下为 DECIMAL(10, 0) ,只保存整数。而且它在存储相同范围的值是会占用更多的空间,所以出于对额外的空间需求和计算成本的考虑,我们只在需要对小数进行精确计算时才使用该类型。

DECIMAL 的最大位数为 65,而且当为 DECIMAL 列指定的值小数点后位数超过小数位数精度范围时,该值将舍入为精度范围。同样地,如果整数部分的宽度大于指定的精度范围,那么也会发生超出列范围的异常而导致无法正常入库,如下:

create table `decimal_t` (`d1` decimal(7, 4) NOT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='DECIMAL';INSERT INTO decimal_t values (3.1415926535);# 结果值为 3.1416INSERT INTO decimal_t values (1234.1415926535);# Data truncation: Out of range value for column 'd1' at row 1

除此之外,在一些大容量的场景下,可以考虑使用 BIGINT 代替 DECIMAL,在存储时根据小数的位数乘以相应的倍数即可。这样就可以同时避免浮点数计算不精确、 DECIMAL 精确计算代价高和数值精度范围限制的问题。


巨人的肩膀

  • 《高性能 MySQL 第四版》:第六章

  • 11.7 Data Type Storage Requirements

  • mysql的日期时间类型及精度问题

  • MySQL之DATETIME与TIMESTAMP的时间精度问题

  • 11.8 Choosing the Right Type for a Column

  • 11.1.4 Floating-Point Types (Approximate Value) - FLOAT, DOUBLE

  • B.3.4.8 Problems with Floating-Point Values

  • 《MySQL 是怎样运行的》:第四章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/26738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IMv1.0

一、背景内容 总结golang基础内容&#xff0c;通过一个实例实时 IM系统简进行总结知识 二、简要的图 简要说明&#xff1a; 1.在server.go中&#xff0c;创建一个Newserver返回server指针的结构体 2.正对这个指针结构体实现两个方法 Handler&#xff08;处理方法&#xff0…

VBA遍历Wrod所有表格每个单元格,单元格未尾两个回车替换

一、遍历 word中遍历所有表格的每个单元格。因为在单元格时会常出错。浪费了不少时间。 Sub a()Dim doc As Document, tb As Table, ce As cellDim rng As Range, p As ParagraphSet doc ActiveDocumentFor Each tb In doc.TablesFor Each ce In tb.Range.Cells 关键处就是这里…

redis入门2-命令

Redis的基本数据类型 redis的基本数据类型&#xff08;value&#xff09;: string,普通字符串 hash&#xff08;哈希&#xff09;,适合存储对象 list(列表),按照插入顺序排序&#xff0c;可以由重复的元素 set(无序集合)&#xff0c;没有重复的元素 sorted set(有序集合)&…

Rust 原生支持龙架构指令集

导读近日&#xff0c;Rust 开源社区发布 1.71.0 版本&#xff0c;实现对龙架构&#xff08;LoongArch&#xff09;指令集的原生支持。 龙架构操作系统发行版和开发者可基于上游社区源代码构建或直接下载 Rust 开源社区发布的龙架构二进制版本。Rust 开发者将在龙架构平台上获得…

【枚举】CF1706 C

有人一道1400写了一个小时 Problem - C - Codeforces 题意&#xff1a; 思路&#xff1a; 首先先去观察样例&#xff1a; 很显然&#xff0c;对于n是奇数的情况&#xff0c;只有一种情况&#xff0c;直接操作偶数位就好了 主要是没搞清楚n是偶数的情况 其实有个小技巧&…

无涯教程-Perl - delete函数

描述 此函数从哈希中删除指定的键和关联的值,或从数组中删除指定的元素。该操作适用于单个元素或切片。 语法 以下是此函数的简单语法- delete LIST返回值 如果键不存在,并且与已删除的哈希键或数组索引关联的值,则此函数返回undef。 Perl 中的 delete函数 - 无涯教程网无…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

p7付费课程笔记6:CMS GC

目录 前言 工作步骤 缺点 问题 前言 上一章节我们讲了串/并行GC&#xff0c;这一章节说下CMS GC。看前思考一个问题&#xff0c;并行GC与CMS GC的区别在哪里。 什么是CMS收集器 CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于…

数据库索引的使用

1、MySQL的基本架构 架构图 左边的client可以看成是客户端&#xff0c;客户端有很多&#xff0c;像我们经常你使用的CMD黑窗口&#xff0c;像我们经常用于学习的WorkBench&#xff0c;像企业经常使用的Navicat工具&#xff0c;它们都是一个客户端。右边的这一大堆都可以看成是…

【C++从0到王者】第十六站:stack和queue的使用

文章目录 一、stack的使用1.stack的介绍2.stack的使用 二、queue的使用1.queue的护额晒2.queue的使用 三、stack和queue相关算法题1.最小栈2.栈的压入、弹出序列3.逆波兰表达式4.两个栈实现一个队列5.用两个队列实现栈6.二叉树的层序遍历1.双队列2.用一个变量levelSize去控制 7…

ECharts 折线图使用相关

一、折线图堆叠设置为不堆叠的方法 官网是这样的&#xff0c;但是不需要这种堆叠形式的如下图&#xff1a; 即&#xff1a;第2条数据值 第1条数据值 第2条数据值 ​​​​​​​ 第3条数据值 第2条数据值 第3条数据值 需要改成实际值展示&#xff0c;如下图&#xff1a; 只…

数据结构之栈和队列---c++

栈和队列的简单介绍 栈 栈是一个“先进后出”结构 队列 入队演示 队列是一种“先进先出”的结构 出队演示 接下来我们开始本次的内容 栈实现队列 分析 1.我们可以老老实实的写一个栈然后将所有的接口函数实现出来&#xff0c;最后再进行实现队列&#xff0c;但是显然…

【雕爷学编程】Arduino动手做(193)---移远 BC20 NB+GNSS模块7

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

【数学建模学习(9):模拟退火算法】

模拟退火算法(Simulated Annealing, SA)的思想借 鉴于固体的退火原理&#xff0c;当固体的温度很高的时候&#xff0c;内能比 较大&#xff0c;固体的内部粒子处于快速无序运动&#xff0c;当温度慢慢降 低的过程中&#xff0c;固体的内能减小&#xff0c;粒子的慢慢趋于有序&a…

空地协同智能消防系统——无人机、小车协同

1 题目 1.1 任务 设计一个由四旋翼无人机及消防车构成的空地协同智能消防系统。无人机上安装垂直向下的激光笔&#xff0c;用于指示巡逻航迹。巡防区域为40dm48dm。无人机巡逻时可覆盖地面8dm宽度区域。以缩短完成全覆盖巡逻时间为原则&#xff0c;无人机按照规划航线巡逻。发…

2019年09月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题 第1题 关于Python的编程环境,下列的哪个表述是正确的? A:Python的编程环境是图形化的; B:Python只有一种编程环境ipython; C:Python自带的编程环境是IDLE; D:用windows自带的文本编辑器也可以给Python编程?,并且也可以在该编辑器下运行; 正确答案…

自动驾驶传感器选型

360的场景&#xff0c;避免有盲区&#xff0c;长距离 Lidar&#xff08;激光雷达&#xff09; 典型特点一圈一圈的&#xff0c;轮廓和很高的位置精度 禾赛的机械雷达 速腾的固态雷达 固态雷达是车规级的&#xff0c;车规级的意思是可以装到量产车上 Radar&#xff08;毫米…

门面模式(C++)

定义 为子系统中的一组接口提供一个一致(稳定) 的界面&#xff0c;Facade模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用(复用)。 应用场景 上述A方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合&#xff0c;随着外部客户程序和各子…

数据结构—哈夫曼树及其应用

5.6哈夫曼树及其应用 5.6.1哈夫曼树的基本概念 路径&#xff1a;从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。 结点的路径长度&#xff1a;两结点间路径上的分支数。 树的路径长度&#xff1a;从树根到每一个结点的路径长度之和。记作 TL 结点数目相同的…

【jvm】jvm整体结构(hotspot)

目录 一、说明二、java代码的执行流程三、jvm的架构模型3.1 基于栈式架构的特点3.2 基于寄存器架构的特点 一、说明 1.hotspot vm是目前市场上高性能虚拟机的代表作之一 2.hotspot采用解释器与即时编译器并存的架构 3.java虚拟机是用来解释运行字节码文件的&#xff0c;入口是字…