MySQL-基础

MySQL 日常使用笔记与备忘

MySQL 5.7 Reference Manual (右上角可切换文档版本 5.6/5.7/8.0) https://dev.mysql.com/doc/refman/5.7/en/

数据库内核月报 - 阿里 MySQL 内核组,文章质量很高 http://mysql.taobao.org/monthly/

叶金荣 - ORACLE MySQL ACE https://imysql.com/


MySQL字段名规范

9.2 Schema Object Names https://dev.mysql.com/doc/refman/5.7/en/identifiers.html

标识符可以用反引号引住,也可以不引住,如果标识符包含特殊字符或者本身是保留字,则必须用反引号引住。 MySQL 内部使用 Unicode(UTF-8) 保存标识符,在 BMP(Basic Multilingual Plane) 中的 Unicode 字符可出现在标识符中,但增补字符不允许。

1、允许出现在无反引号标识符中的字符:

  • ASCII 字符,[0-9,a-z,A-Z$_] 大小写字母、数字、dollar符、下划线
  • U+0080 到 U+FFFF 的 Unicode 字符

2、允许出现在反引号标识符中的字符,包括除了 U+0000 外的全部 BMP 字符:

  • U+0001 到 U+007F 的 ASCII 字符
  • U+0080 到 U+FFFF 的 Unicode 字符

3、ASCII NUL (U+0000) 字符和高于 U+10000 的增补字符不允许出现在标识符中,无论是否带反引号。

4、标识符可以以数字开头。除非反引号引住否则不允许全数字的标识符。

5、库名、表名、列表不能以空字符结尾。

全数字列名必须反引号引起来

表名、列名可以是全数字,但必须用反引号引起来,否则会报错

CREATE TABLE `474274538` (
    id     BIGINT(20) NOT NULL AUTO_INCREMENT,
    `123`  VARCHAR(255),
    PRIMARY KEY (id)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8

表名最长64字符

9.2.1 Identifier Length Limits https://dev.mysql.com/doc/refman/5.7/en/identifier-length.html

数据库、表、列和索引的名称最长可达64个字符。别名最长可达256个字符。

用函数名做列名时必须反引号引住

下面的 sql 会报错,因为添加的列名 current_time 是 MySQL 的一个 函数名

alter table t1 add column current_time varchar(255);

报错: [42000][1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'current_time varchar(255)' at line 1

解决: 使用反引号将 current_time 引起来:

alter table t1 add column `current_time` varchar(255);

设计与实践

存储树形结构

一般比较普遍的就是四种方法:(具体见 SQL Anti-patterns这本书)

  • Adjacency List:邻接表,每一条记录存parent_id
  • Path Enumerations:路径枚举,每一条记录存整个tree path经过的node枚举
  • Nested Sets:每一条记录存 nleft 和 nright
  • Closure Table:闭包表,维护一个表,所有的tree path作为记录进行保存。

怎样在 MySQL 表中存储树形结构数据? - 卢钧轶的回答 - 知乎 https://www.zhihu.com/question/20417447/answer/15078011

树状结构的数据表如何设计? https://segmentfault.com/q/1010000000126370

干货:在关系型数据库中优雅地存储树形结构 https://www.jianshu.com/p/951b742fd137

用大间隔int来表示状态码

比如一个实体类表5种有序的状态,可以用status表示

`status` TINYINT NOT NULL DEFAULT 0
COMMENT '结点状态,0-未知,1-阶段1,2-阶段2,3-阶段3,4-阶段4,5-阶段5',

假如这是一个订单的状态的话,这样设计有一个潜在的风险,就是如果之后需求发生变化,需要再插入中间状态,就需要刷数据了,因为间隔为1的int中间再无法再插入值了。 所以建议设计为

`status` TINYINT NOT NULL DEFAULT 0
COMMENT '结点状态,0-未知,10-阶段1,20-阶段2,30-阶段3,40-阶段4,50-阶段5',

用间隔为10的int来表示状态码,哪天需求变了要加中间状态就很方便,比如可以在10和20直接加一个15状态码。


给一个很大的线上表加字段并刷数据

给一个很大的线上表加字段,并且加字段后要设置初始化数据,要考虑哪些因素?怎么做?

考虑: 1、加字段是否会锁表? 2、加字段后设置初始值是否需要更新表的update_time? 比如 加字段 a,根据某些条件 set a 的初始值后,表中所有行的update_time都会更新为sql脚本执行时间,如果业务逻辑中有根据 update_time 做判断的,需要仔细考虑: (1)比如我们系统中有个根据 update_time 拉取最近 24 小时内有更新的数据的接口,如果刷数据后全量数据的 update_time 都更新为当前时间,会导致这个接口能遍历出全量数据,影响性能。 (2)比如有的表的查询逻辑需要按 update_time 排序后取最新的一个,如果刷数据后所有记录 update_time 都变为一样的,就会影响业务逻辑。 为了避免这种情况,刷数据时可以保留原有的 update_time ,如下:

update table t
set t.a = xx, t.update_time = t.update_time
where xxx

一种方案: 1、先拷贝出镜像表 2、服务中双写 3、改名替换

如果用这个方案,需要注意什么? 1、双写必须在一个事务中 2、两个表里的自增id是无法对齐的,需要处理


三方工具

Yearning MYSQL SQL语句审核平台

https://guide.yearning.io/


运维

3.5亿数据上执行truncate耗时4分钟

3.5亿数据,占用180g磁盘空间,线上有高频写入(但没有删除和更新) truncate 耗时 177 秒。

3亿mysql表单字段加索引约40分钟-1个半小时

2亿8709万数据mysql表,单字段加索引,耗时40分钟53秒 alter table tt add index idx_column1(column1);

3亿2900万数据mysql表,create_time 字段加索引,耗时1小时35分钟。

3亿2900万数据mysql表,create_time 字段加索引,耗时1小时16分钟。

2.6亿mysql表改字段名和类型约4个半小时

2亿6581万数据mysql表, modified_date 字段原来是 datetime 类型,改为 datetime(3) 同时名字改为 update_time 。 modified_date 字段上无索引,并且 t1 表无任何其他读写。 SQL如下 alter table t1 change column modified_date update_time datetime(3) not null comment '更新时间'; 耗时4小时23分钟。

1亿mysql表全表单字段修改耗时约3小时

update table_test1 set device_id = concat('device_', id%100); 1亿数据,表 20+ 列,单行数据约 2kb, 执行sql耗时 2 小时 45 分钟 1000万数据,表 20+ 列,单行数据约 2kb, 执行sql耗时 6分半 500万数据,表 20+ 列,单行数据约 2kb, 执行sql耗时 3 分钟

20亿mysql表单字段加索引约16小时

800万mysql表单字段加索引1分30秒

表10+字段,没有很长的text字段,数据持续读写更新中 在引用其他表id的字段(user_id bigint)上加索引,耗时 1分30秒

1236 error: Could not find first log file name in binary log index file

原因:从库读取主库 binlog 出错 有可能是mysql服务器异常断电造成的,也可能是有人手动删除了master上的binlog文件,

解决: 重建从库

ERROR 1010 (HY000): Error dropping database (can't rmdir './test/', errno: 17)

删除db时报错: ERROR 1010 (HY000): Error dropping database (can't rmdir './test/', errno: 17)

原因: test目录下存在着MySQL数据库不知道的文件,即MySQL数据库中没有该文件的数据字典信息。

解决: 直接去data目录下删除对应的 test 目录 https://stackoverflow.com/questions/4584458/error-dropping-database-cant-rmdir-test-errno-17

mysql删除数据后何时释放磁盘空间?

drop table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM ; truncate table table_name立刻释放磁盘空间 ,不管是 Innodb和MyISAM; delete from table_name 删除表的全部数据,对于MyISAM 会立刻释放磁盘空间 ,而InnoDB 不会释放磁盘空间; delete from table_name where xx 带条件的删除, 不管是innodb还是MyISAM都不会释放磁盘空间; delete 操作以后使用 optimize table table_name 会立刻释放磁盘空间。不管是innodb还是myisam 。所以要想达到释放磁盘空间的目的,delete以后执行optimize table 操作。优化表期间会锁定表,所以要在空闲时段执行optimize table ,测试十几个G数据的表执行optimize table 大概20多分钟。

delete from表以后虽然未释放磁盘空间,但是下次插入数据的时候,仍然可以使用这部分空间。

delete删除数据的时候,mysql并没有把数据文件删除,而是将数据文件的标识位删除,没有整理文件,因此不会彻底释放空间。被删除的数据将会被保存在一个链接清单中,当有新数据写入的时候,mysql会利用这些已删除的空间再写入。即,删除操作会带来一些数据碎片,正是这些碎片在占用硬盘空间。

OPTIMIZE TABLE命令优化表,该命令会重新利用未使用的空间,并整理数据文件的碎片;该命令将会整理表数据和相关的索引数据的物理存储空间,用来减少占用的磁盘空间,并提高访问表时候的IO性能;但是具体对表产生的影响是依赖于表使用的存储引擎的。该命令对视图无效。

innodb log sequence number is in the future

mysql掉电后无法启动 InnoDB: is in the future http://blog.cuicc.com/blog/2015/10/12/mysql-can-not-startup-after-loss-power/


show master status

show master status 用于看主从同步中的主库状态,只有在主库上执行才有结果。 结果中可以看到当前使用的 binlog 文件和 偏移量 position 数字

mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
             File: source-bin.000002
         Position: 1307
     Binlog_Do_DB: test
 Binlog_Ignore_DB: manual, mysql
Executed_Gtid_Set: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
1 row in set (0.00 sec)

File: mysql-master-bin.000001//当前正在写入的binlog文件 Position: 1112 //当前正在写入的位置。 Binlog_Do_DB:// 只记录指定数据库的二进制文件。 Binlog_Ignore_DB://不记录指定数据库的二进制文件 Executed_Gtid_Set://5.7在mysql库下引入了新的表gtid_executed,会记录当前执行的GTID。

https://dev.mysql.com/doc/refman/5.7/en/show-master-status.html


数据库性能测试

sysbench – oltp 测试

tpc-c – 事务性能测试

tpc-h – olap 测试

挑战MySQL建立10万连接

MySQL Challenge: 100k Connections https://www.percona.com/blog/2019/02/25/mysql-challenge-100k-connections/

【译】MySQL挑战:建立10万连接 https://juejin.cn/post/6844903793360896013


MySQL 8.0

窗口函数

窗口函数(window functions)是数据库的标准功能之一,主流的数据库比如Oracle,PostgreSQL都支持窗口函数功能。 窗口函数是一种特殊的函数,它可以在一组查询行上执行类似于聚合的操作,但是不会将查询行折叠为单个输出行,而是为每个查询行生成一个结果。 窗口函数可以用来处理复杂的报表统计分析场景,例如计算移动平均值、累计和、排名等

JSON 支持

MySQL8.0 版本开始正式支持 JSON 相关的语法,提供了 JSON 格式的数据库字段类型

  • 自动验证。错误的JSON格式会报错。
  • 存储格式优化。数据保存为二进制格式,文件存储很紧凑,读取速度快。
  • MySQL可以通过键或数组索引查询和修改对应的值,不用把整个字符串都读出来。

Instant Add Column 快速加列

在MySQL 8.0版本中,不论表的数据量有多大,加列操作都能瞬间完成,这就是内核原生的快速加列(Instant Add Column)功能。有了快速加列以后,直接通过SQL语句添加列即可。加列以后,MySQL并不会真的去把每一行数据都修改一遍,仅仅是在元数据中增加一列。在读取时,MySQL会返回这一列的默认值,在以后修改时,会将真实内容写入到数据文件中。