MySQL · 简介
全球数据库排行
MySQL
发展历史
- 1979年,Monty 写了最初的版本
- 1996年,发布 1.0 版本
- 1999年,MySQL AB 在瑞典正式宣布成立
- 2000年4月,集成 MyISAM 和 Replication
- 2003年,MySQL4.0 发布,正式集成 InnoDB
- 2005年,发布 5.0 版本(提供了视图、存储过程等功能)
- 2006年,MySQL 被 Sun 收购,出价 10 亿美元
- 2009年,Oracle 公司收购 Sun,将 MySQL 纳入囊中
- 2010年12月,发布 5.5 正式版本,InnoDB 改为默认存储引擎
- 2013年2月,发布 5.6 正式版本
- 2015年10月,发布 5.7 正式版本
- 2018年5月,发布 8.0 正式版本
优缺点
优点
- 免费
- 开源(源码、社区工具丰富)
- 简单(部署简单、门槛低、上手快)
缺点
- 优化器对复杂 SQL 支持不好
- 大规模集群方案不成熟,主要指中间件
- 备份和恢复方案还是比较复杂,需要依赖外部组件
- 缺少分库分表解决方案
行业应用
主流分支
基本概念
数据库:就是一个存储结构化数据的仓库。物理操作系统文件或其他形式文件类型的集合,通常来说存在与文件系统之上。
关系型数据库:是创建在关系模型基础上的数据库,典型代表有:MySQL、Oracle、Microsoft SQL Server、PostgreSQL 等。
非关系型数据库(NoSQL):对不同于传统的关系数据库的数据库管理系统的统称。与关系数据库最大的不同点是不使用SQL作为查询语言。 典型代表有:Redis、MongoDB 等。
实例:MySQL 数据库由后台线程以及一个共享内存区组成。数据库实例才是真正用于操作数据库文件的。MySQL 数据库实例在系统上的表现就是一个进程。
库:本质是一个文件夹。
表:本质就是一个文件。
记录:相当于文件中的一行。
存储引擎:可以理解成文件系统,存储引擎的对象就是表。
OLAP:On-Line Transaction Processing,联机事务处理。
OLTP:On-Line Analytical Processing,联机分析处理。
DML:数据操作语言,主要是用来对数据进行一些操作,如 SELECT、UPDATE、INSERT、DELETE 等。
DDL:数据定义语言,主要是用在定义或改变表的结构,如 CTEATE、ALTER、DROP、TRUNCATE 等。
学习资料
MySQL · 分库分表
背景
由于单机存储容量、连接数、处理能力有限,当超过一定上限后,数据库会遭遇性能瓶颈,即使优化索引,很多操作的性能仍下降严重。切分 (Sharding) 的目的就在于减少数据库的负担,缩短查询时间。
根据切分类型,可以分为两种方式:垂直切分和水平切分。
分库分表
垂直切分
① 垂直分库
概念:根据业务耦合性,将关联度低的不同表存储在不同的数据库。与”微服务治理”的做法相似,每个微服务使用单独的一个数据库。
结果:
- 每个库的结构、数据都不一样。
- 所有库的并集是全量数据。
场景:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
② 垂直分表
概念:某个表字段较多,以列为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。
结果:
- 每个表的结构、数据都不一样;
- 所有表的并集是全量数据;
场景:表字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读 IO,产生 IO 瓶颈。
分析:拆了之后,要想获得全部数据就需要关联两个表来取数据。但记住,千万别用 join,因为 join 不仅会增加CPU 负担并且会将两个表耦合在一起(必须在一个数据库实例上)。关联数据,应该在业务 service 层做文章,分别获取主表和扩展表数据然后用关联字段关联得到全部数据。
③ 优缺点
优点:
- 解决业务系统层面的耦合,业务清晰。
- 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等。
缺点:
- 部分表无法 join,只能通过接口聚合方式解决,提升了开发的复杂度。
- 分布式事务处理复杂。
- 依然存在单表数据量过大的问题(需要水平切分)。
水平拆分
① 水平分库
概念:当一个应用数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。以字段为依据,按照一定策略(hash、range 等),将一个库中的数据拆分到多个库中。
结果:
- 每个库的结构都一样;
- 每个库的数据都不一样,没有交集;
- 所有库的并集是全量数据;
场景:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。
② 水平分表
概念:以字段为依据,按照一定策略(hash、range等),将一个表中的数据拆分到多个表中。
结果:
- 每个表的结构都一样;
- 每个表的数据都不一样,没有交集;
- 所有表的并集是全量数据;
场景:解决了单一表数据量过大的问题。
分析:表的数据量少了,单次 SQL 执行效率高,自然减轻了 CPU 的负担。
③ 优缺点
优点:
- 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力。
- 应用端改造较小,不需要拆分业务模块。
缺点:
- 跨库的 join 关联查询性能较差。
- 数据多次扩展难度和维护量极大。
几种典型的数据水平分片规则:
根据数值范围:按照时间区间或 ID 区间切分,例如:按日期将不同月甚至是日的数据分散到不同的库表中,将 userId 为 1
9999 的记录分到第一个库或表,1000020000 的分到第二个库或表。优点:扩容简单
缺点:请求量分布不均匀,导致服务器利用率不平衡
根据数值取模:一般采用 hash 取模 mod 的切分方式,例如:将 Customer 表根据 cusno 字段切分到 4 个库中,余数为 0 的放到第一个库,余数为 1 的放到第二个库。
优点:数据量和请求量分布均匀
缺点:扩容麻烦,需要考虑对数据进行平滑的迁移
分库分表带来的问题
分库分表能有效的环节单机和单库带来的性能瓶颈和压力,突破网络 IO、硬件资源、连接数的瓶颈,同时也带来了一些问题。
事务一致性问题:往往不苛求系统的实时一致性,只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。
跨节点关联查询 join 问题:考虑到性能,尽量避免使用 join 查询,一般通过字段冗余反范式设计、数据组装等方法。
跨节点分页、排序、函数问题:需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。
全局主键避重问题:由于表中数据同时存在不同数据库中,主键值平时使用的自增长将无用武之地,某个分区数据库自生成的 ID 无法保证全局唯一。因此需要单独设计全局主键,以避免跨库主键重复问题。一些常见的主键生成策略:
工具中间件
实践
原则
- 原则 0:能不分就不分。
- 原则 1:数据量太大,正常的运维影响正常的业务访问。
- 原则 2:表设计不合理,需要对某些字段进行垂直拆分。
- 原则 3:某些数据表出现了无穷增长的情况。
- 原则 4:安全性和可用性的考虑。
- 原则 5:业务耦合性考虑。
总结
① 水平拆分和垂直拆分都是降低数据量大小,提升数据库性能的常见手段。
② 流量大数据量大时,数据访问要有 service 层,并且 service 层不要通过 join 来获取主表和扩展表的属性。
③ 垂直拆分的依据,尽量把长度较短,访问频率较高的属性放在主表里。
MySQL · 备份与恢复
概述
根据备份方法不同:
- 热备:数据库运行过程中直接备份,对正在运行的数据库操作没有任何的影响。
- 冷备:备份操作是在数据库停止的情况下,这种备份最为简单,只需要复制相关的数据库物理文件即可。
- 温备:数据库运行过程中直接备份,但是会对当前数据库操作有所影响。
根据备份后文件的内容不同:
- 逻辑备份:是指备份出的文件内容是可读的,一般内容是 SQL 语句,如 mysqldump。
- 物理备份:是指复制数据库的物理文件,如 xtrabackup。
根据备份数据库内容不同:
- 全量备份:是指对数据库进行一个完整的备份。
- 增量备份:是指在上次完全备份的基础上,对于更改的数据进行备份。
- 日志备份:是指对二进制日志的备份,通过它可以完成 point-in-time 的恢复工作。
自建 MySQL
备份
物理备份
逻辑备份
恢复
阿里云 RDS
备份
全量备份(数据)
增量备份(日志)
恢复
单库单表恢复
整实例恢复
恢复到原实例
恢复到新实例
恢复到本地自建数据库
备份
物理备份
备份内容:共享表空间文件 .frm
、独立表空间文件 .ibd
、重做日志文件、配置文件 my.cnf
。
优缺点:
- 优点:备份恢复简单、速度快。
- 缺点:备份的文件通常比逻辑文件大很多,且需要停机,不适于用于生产环境。
逻辑备份
mysqldump
语法
1 | Usage: mysqldump [OPTIONS] database [tables] > [backupfile.sql] |
参数
–single-transaction:在备份之前,先执行 start transaction 命令,以此来获得备份的一致性,当前该参数只对 InnoDB 存储引擎有效。当启用此参数时,确保没有任何 DDL 语句执行,因为一致性读并不能隔离 DDL 操作。
–lock-tables (-l):在备份中,依次锁住每个库下所有表,一般用于 MyISAM 存储引擎,当备份时只对数据库进行读取操作,不过备份依然可以保证一致性。对于 InnoDB 存储引擎,不需要使用该参数。
–lock-all-tables (-x):在备份中,对所有库的所有表上锁。
–no-data (-d):不备份数据。
–extended-insert (-e):使用全新多行 insert 语法,默认开启。
–master-data [=value]:通过该参数产生的备份转存文件主要用来建立一个 replication。
1:转存文件中记录 change master 语句。
2:转存文件中 change master 语句被注释。
–flush-logs:开始备份之前刷新日志。
–events (-E):备份事件。
–routines (-R):备份存储过程和函数。
–triggers:备份触发器,该选项默认启用。
–where=’where_condition’ (-w ‘where_condition’):导出给定条件的数据。
实践
1 | # 备份所有库 |
注意:默认是会备份视图。
恢复
二进制日志备份与恢复
作用:二进制日志非常关键,用户可以通过它完成 point-in-time 的恢复工作。对于 InnoDB 存储引擎,推荐的二进制日志的服务器配置应该是:
1 | [mysqld] |
备份:在备份二进制日志之前,可以通过 Flush Logs 命令生成一个新的二进制日志文件,然后后备份之前的二进制日志。
恢复:二进制日志的恢复主要通过工具 mysqlbinlog 实现。例如:
1 | // 恢复单个文件 |
热备
快照备份
MySQL 数据库本身并不支持快照功能,因此快照备份是指通过文件系统支持的快照功能对数据库进行备份。备份的前提是将所有数据库文件放在同一个文件分区中,然后对该分区进行快照操作。支持快照功能的文件系统和设备包括 GUN/Linux 的逻辑管理器 LVM。
LVM 是 Linux 系统下对磁盘分区进行管理的一种机制,LVM 在硬盘和分区之上建立一个逻辑层,来提高磁盘分区管理的灵活性。LVM 使用了写时复制(Copy-on-write)技术来创建快照,当创建一个快照时,仅复制原始卷中数据的元数据(meta data),并不会有数据的物理操作,因此快照的创建过程非常快。
使用 LVM 快照备份 InnoDB 存储引擎表非常简单,只要把与 InnoDB 存储引擎相关的文件如共享表空间、独立表空间、重做日志文件等放在同一个逻辑卷中,然后对这个逻辑卷做快照备份即可。在对 InnoDB 存储引擎文件做快照时,数据库无须关闭,即可以进行在线备份。
全量备份
增量备份
备份策略
参数 | 说明 |
---|---|
备份周期 | 建议一周至少需要备份两次(例如:周一、三、五 / 二、四、六) |
备份时间 | 建议设置为业务低峰期时间 |
保留时间 | 建议保留 30 天,超过保留天数的备份会被自动删除 |
日志备份 | 建议打开,关闭日志备份会导致所有日志被清除,并且无法使用按时间点恢复数据的功能 |
日志备份保留 | 日志备份文件保留的天数,默认为 7 天 |
单库单表 | 开通后会修改备份格式以支持该功能,默认为开启,无法关闭 |
实例释放后备份文件是否保留 | 为防止忘记续费、误操作等情况导致实例数据丢失,建议选择保留最后一个或全部保留,保留不收取费用,可以在已删除实例备份页面下载备份文件,并恢复到本地数据库 |
恢复
恢复方案概览
RDS 提供了多种数据恢复方案,可以将数据恢复至新实例、原实例或自建数据库。
适用场景 | 方案 |
---|---|
MySQL 单库单表恢复 | |
MySQL 实例恢复 | |
MySQL 备份文件恢复到自建数据库 |
单库单表恢复
RDS MySQL 支持单库和单表的数据恢复,可以通过备份指定恢复误删的数据库或表,快速恢复 MySQL 的数据。
注意事项:
- 恢复到原实例过程中会进行主备切换,RDS 服务可能会出现闪断,请确保您的应用有自动重连机制;恢复到新实例不会进行主备切换。
- 超出免费备份空间额度的部分将会产生额外费用,请合理设计备份周期,以满足业务需求的同时,兼顾备份空间的合理利用。
- 如果无法确定所有涉及的表,则需要进行实例级别的恢复。
- 每次最多选择 50 个库或者表。
- 实例内的表低于 50000 张才可以使用单库单表恢复功能,超过 50000 张表时无法使用。
实例恢复
总结
任何时候都需要做好远程异地备份,也就是容灾的防范。
MySQL · 开发规范
库表规范
存储引擎必须使用
InnoDB
库名、表名、字段名必须用小写,采用下划线分隔
表字符集默认使用
utf8
,必要时候使用utf8mb4
1
2
3解读:
① 通用,无乱码风险,汉字3字节,英文1字节
② utf8mb4是utf8的超集,存储4字节,例如:表情符号时使用它禁止使用存储过程、视图、触发器、事件
1
2
3解读:
① 对数据库性能影响较大,能让站点层和服务层干的事情,不要交到数据库层
② 难以调试和扩展,更没有移植性禁止在数据库中存储大文件
1
2解读:
① 例如照片,可以将大文件存储在对象存储系统,数据库中存储路径禁止使用
text
、blob
类型1
2解读:
① 会浪费更多的磁盘和内存空间,非必要的大量的大字段查询会淘汰掉热数据,导致内存命中率急剧降低,影响数据库性能根据业务区分使用
tinyint
(1 byte)、smallint
(2 byte)、int
(4 byte)、bigint
(8 byte)1
2
3解读:
① 合适的字符存储长度,不但节约数据库表空间,节约索引存储,更重要的是提升检索速度
② 整数类型指定显示宽度,例如:int(M)对大多数应用是没有意义的,他不会限制值的合法范围,对于存储和计算来说,int(1)和int(20)是相同的对象 年龄区间 类型 范围 字节 人 150岁之内 unsigned tinyint 0~255 1 龟 数百岁 unsigned smallint 0~65535 2 恐龙化石 数千万岁 unsigned int 0~4294967295 4 太阳 约50亿年 unsigned bigint 0~2^64-1 8 根据业务区分使用
char
、varchar
1
2
3解读:
① 字段长度固定,或者长度近似的业务场景,适合使用char,能够减少碎片,查询性能高
② 字段长度相差较大,或者更新较少的业务场景,适合使用varchar,节省磁盘空间根据业务区分使用
datetime
、timestamp
1
2解读:
① 前者占用8个字节,后者占用4个字节,存储年使用YEAR,存储日期使用DATE,存储时间使用datetime小数类型使用
decimal
,禁止使用float
和double
1
2
3解读:
① float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果
② decimal(M,D),如果M>D,占用字节M+2,否则为D+2,M是数字最大位数,D是小数点右侧数字个数表必须有主键,推荐使用
unsigned
整数为主键1
2解读:
① 删除无主键的表,如果是row模式的主从架构,从库会挂住创建是否概念字段,必须使用
is_xxx
的方式命名,数据类型是unsigned tinyint
( 1表示是,0表示否)表必备三字段:
id
,gmt_create
,gmt_modified
1
2
3解读:
① id必为主键,类型为unsigned bigint,单表时自增,步长为1
② gmt_create, gmt_modified类型均为datetime,前者现在时表示主动创建,后者过去分词表示被动更新字段定义必须为
NOT NULL
,并设默认值1
2
3
4解读:
① NULL列使用索引,会影响优化器选择执行计划,很难优化
② NULL只能采用IS NULL或者IS NOT NULL,而在使用=、!=、in、not in时有大坑
③ 数值默认值设置为0,字符串默认值设置为''使用
unsigned int
存储IPv4
,不要用char(15)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20解读:
① 节省存储空间,char(15)占据16个字节,unsigned int只需要4个字节
② int型在逻辑运算上,比char速度快
③ ip和数字转换,函数算法:inet_aton()和inet_ntoa()
mysql> select inet_aton('192.168.1.200');
+----------------------------+
| inet_aton('192.168.1.200') |
+----------------------------+
| 3232235976 |
+----------------------------+
1 row in set (0.00 sec)
mysql> select inet_ntoa('3232235976');
+-------------------------+
| inet_ntoa('3232235976') |
+-------------------------+
| 192.168.1.200 |
+-------------------------+
1 row in set (0.00 sec)使用
varchar(20)
存储手机号,不要使用整数1
2
3
4解读:
① 牵扯到国家代号,可能出现+/-/()等字符,例如+86
② 手机号不会用来做数学运算
③ varchar可以模糊查询,例如:like '138%'控制单表数据量,单表数据控制在千万级
测试、开发、线上数据库环境必须隔离
数据表、数据字段要有中文注释
SQL规范
禁止使用
select *
,只获取必要字段1
2
3
4解读:
① select*会增加cpu/io/内存/带宽的消耗
② 指定字段能有效利用索引覆盖
③ 指定字段查询,在表结构变更时,能保证对应用程序无影响禁止在
where
条件列使用函数或者表达式1
2解读:
① 导致不能命中索引,全表扫描禁止负向查询,以及
%
开头的模糊查询1
2
3解读:
① 负向查询条件:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等,会导致全表扫描
② %开头的模糊查询,会导致全表扫描不得使用外键与级联,一切外键概念必须在应用层解决
count(*)
会统计值为null
的行,而count(列名)
不会统计此列为null
值的行数据订正(
delete
、update
操作)时,要先select
,避免出现误删除,确认无误才能执行更新语句推荐使用
truncate
,速度比delete
快,且使用的系统和事务日志资源少1
2
3解读:
① truncate table在功能上与不带where子句的delete语句相同
② truncate无事务且不触发trigger,有可能造成事故避免
in
操作,若实在避免不了,需要仔细评估in
后边的集合元素数量,控制在1000
个之内推荐
or
改写为union
索引规范
唯一索引使用
uniq_[字段名]
来命名,非唯一索引使用idx_[字段名]
来命名单张表索引数量建议控制在
5
个以内1
2
3
4解读:
① 高并发业务,太多索引会影响写性能
② 生成执行计划时,如果索引太多,会降低性能,并可能导致MySQL选择不到最优索引
③ 异常复杂的查询需求,可以选择ES等更为适合的方式存储超过三个表禁止
join
,需要join
的字段,数据类型必须绝对一致,多表关联查询时,确保被关联的字段需要有索引1
2解读:
① 踩过因为join字段类型不一致,而导致全表扫描的坑禁止在更新十分频繁,区分度不高的字段建立索引
1
2
3
4解读:
① 更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能
② 区分度:count(distinct left(列名, 索引长度))/count(*)
③ 性别不适合建索引理解组合索引最左前缀原则,避免重复建索引,如果建了
(a,b,c)
,相当于建了(a)
,(a,b)
,(a,b,c)
1
2
3解读:
① 示例:where a=? and b=? order by c; 索引:a_b_c
② 存在非等号和等号混合判断条件时,在建索引时把等号条件的列前置。如:where a>? and b=? 那么即使a的区分度更高,也必须把b放在索引的最前列避免因字段类型不同造成的隐式转换,导致索引失效
SQL性能优化的目标:至少要达到
range
级别,要求是ref
级别,如果可以是consts
最好1
2
3
4解读:
① consts单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据
② ref指的是使用普通的索引(normal index)
③ range对索引进行范围检索
参考
MySQL · InnoDB 数据存储
基本概念
InnoDB
存储引擎可将所有数据存放于 ibdata*
的共享表空间,也可将每张表存放于独立的 .ibd
文件的独立表空间。共享表空间以及独立表空间都是针对数据的存储方式而言的。
共享表空间: 某一个数据库的所有的表数据,索引文件全部放在一个文件中,默认这个共享表空间的文件路径在 data
目录下, 默认的文件名为 ibdata1
,初始化为 10M
。
独立表空间: 每一个表都将会生成以独立的文件方式来进行存储,每一个表都有一个 .frm
表描述文件,还有一个 .ibd
文件。 其中这个文件包括了单独一个表的数据内容以及索引内容,默认情况下它的存储位置也是在表的位置之中。
优缺点
共享表空间
优点:可以将表空间分成多个文件存放到各个磁盘上(表空间文件大小不受表大小的限制,如一个表可以分布在不同的文件上),数据和文件放在一起方便管理。
缺点:所有的数据和索引存放到一个文件中,虽然可以把一个大文件分成多个小文件,但是多个表及索引在表空间中混合存储,这样对于一个表做了大量删除操作后表空间中将会有大量的空隙,特别是对于统计分析这类应用最不适合用共享表空间。
独立表空间:在配置文件(my.cnf
)中设置 innodb_file_per_table
优点:
- 每个表都有自已独立的表空间。
- 每个表的数据和索引都会存在自已的表空间中。
- 可以实现单表在不同的数据库中移动。
- 空间可以回收(除 drop table 操作处,表空不能自已回收)。
缺点
- 单表增加过大,如超过100G。
- 相比较之下,使用独占表空间的效率以及性能会更高一点。
参数详解
Command-Line Format | --innodb_file_per_table |
|
---|---|---|
System Variable | Name | innodb_file_per_table |
Variable Scope | Global | |
Dynamic Variable | Yes | |
Permitted Values (<= 5.6.5) | Type | boolean |
Default | OFF |
|
Permitted Values (>= 5.6.6) | Type | boolean |
Default | ON |
修改 InnoDB 数据存储方式:
1 | mysql> set global innodb_file_per_table =ON; |
想要将共享表空间转化为独立表空间有两种方法:
先逻辑备份,然后修改配置文件
my.cnf
中的参数innodb_file_per_table
参数为1
,重启服务后将逻辑备份导入即可。修改配置文件
my.cnf
中的参数innodb_file_per_table
参数为1
,重启服务后将需要修改的所有innodb
表都执行一遍:alter table table_name engine=innodb;
使用第二种方式修改后,原来库中的表中的数据会继续存放于ibdata1
中,新建的表才会使用独立表空间。
操作实战
1 | # 导出所有数据 |
MySQL · 存储引擎
简介
MySQL 数据库区别于其他数据库的最重要的一个特点就是其插件式的表存储引擎。
我们知道关系型数据库的数据是存在表里的,可以将表理解为由行和列组成的表格,类似于 Excel 的电子表格的形式,每个表格就是一个数据。表在存储数据的同时,还要组织数据的存储结构,而这些数据的组织结构就是由存储引擎决定的。简单来说,存储引擎的作用就是规定了数据的存储结构,由实际业务决定。
插件式存储引擎的好处是,每个存储引擎都有各自的特点,可以根据具体的应用建立不同的存储引擎表,这也是 MySQL 的强大之处。
需要特别注意的是,存储引擎是基于表的,而不是数据库。
MySQL 存储引擎
MySQL 支持多种存储引擎,可以通过 show engines
语句来查看。日常开发中用到最多的存储引擎是 InnoDB 与 MyISAM 两种,都是以插件的形式集成在数据库中。
1 | mysql> show engines; |
InnoDB
InnoDB 存储引擎支持事务,其设计目标主要面向 OLTP 的应用。特点是行锁设计、支持外键。从 MySQL 5.5.8 版本开始,InnoDB 是 MySQL 默认的存储引擎。
InnoDB 通过使用多版本并发控制 MVCC 来获得高并发性,并且实现了 SQL 标准的 4 种隔离级别,默认为 REPEATABLE 级别。同时,使用一种被称为 next-key-locking 的策略来避免幻读的产生。除此之外,InnoDB 存储引擎还提供了插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等高性能和高可用功能。
对于表中数据的存储,InnoDB 存储引擎采用了聚集(cluster)的方式,因此每张表的存储都是按照主键的顺序进行存放。如果没有显式地在表定义时指定主键,InnoDB 存储引擎会为每一行生成一个 6 字节的 ROWID,并以此作为主键。
MyISAM
MyISAM 存储引擎不支持事务、表锁设计,支持全文索引,主要面向一些 OLAP 应用。在 MySQL 5.5.8 之前,MyISAM 是默认的存储引擎。此外,MyISAM 存储引擎的另一个与众不同的地方在它的缓冲池只缓存(cache)索引文件,而不缓冲数据文件,这一点和大多数数据库都非常不同。
MyISAM 存储引擎表由 MYD 和 MYI 组成,MYD 用来存放数据文件,MYI 用来存放索引文件。
NDB
NDB 存储引擎是一个集群存储引擎,类似于 Oracle 的 RAC 集群。其特点是数据全部存放在内存中,因此主键查找的速度极快,并且通过添加 NDB 数据存储节点可以线性地提高数据库性能,是高可用、高性能的集群系统。
关于 NDB 存储引擎,有一个问题值得注意,那就是 NDB 存储引擎的连接操作 JOIN 是在 MySQL 数据库层完成的,而不是在存储引擎层完成。这意味着,复杂的连接操作需要巨大的网络开销,因此查询速度很慢。
Memory
Memory 存储引擎将表中数据存放在内存中,如果数据库重启或发生崩溃,表中数据都将消失。它非常适合用于存储临时数据的临时表。Memory 存储引擎默认使用哈希索引,而不是 B+ 树索引。
虽然 Memory 存储引擎速度非常快,但在使用上还是有一定的限制。比如只支持表锁,并发性能较差,并且不支持 TEXT 和 BLOB 列类型,最重要的是,存储变长字段 varchar 时是按照定长字段 char 方式进行,因此会浪费内存。
Archive
Archive 存储引擎只支持 INSERT 和 SELECT 操作,使用 zlib 算法将数据行进行压缩后存储,压缩比一般可达 1:10,Archive 存储引擎非常适合存储归档数据,如日志信息。其设计目标主要是提供高速的插入和压缩功能。
InnoDB 与 MyISAM 对比
- 「事务」: InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句,MyISAM 不支持事务。
- 「并发」: MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- 「外键」: InnoDB 支持外键。
- 「备份」: InnoDB 支持在线热备份。
- 「崩溃恢复」: MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 「其它特性」: MyISAM 支持压缩表和空间数据索引。
浅谈数据库设计
数据库设计
设计原则:在数据冗余和处理速度之间找到合适的平衡点。
数据库表个数越少越好。
表中组合主键的字段个数越少越好。主键的作用,一是建主键索引,二是做为子表的外键,所以组合主键的字段个数少了,不仅节省了运行时间,而且节省了索引存储空间。
3)一个表中的字段个数越少越好。
设计模式
逻辑设计:根据数据实体之间的逻辑关系对表进行设计。
物理设计:根据所使用的数据库特点进行表结构设计。
范式设计
- 第一范式:是对属性的原子性约束,要求属性具有原子性,不可再分解。
- 第二范式:是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性。
- 第三范式:是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。
基本表及其字段之间的关系,应尽量满足第三范式。但是,满足第三范式的数据库设计,往往不是最好的设计。为了提高数据库的运行效率,常常需要降低范式标准,适当增加冗余,达到以空间换时间的目的。
逻辑设计
E-R 模型图:结构清晰、关联简洁、实体个数适中、属性分配合理、没有低级冗余
物理设计
数据库类型
数据库类型 | 成本 | 开发语言 | 支持系统 | 业务场景 |
---|---|---|---|---|
Oracle | 商业型 | php,java,python等 | windows/liunx系统 | 企业级 |
SQLServer | 商业型 | .NET,C#等 | 只支持windows系统 | 企业级 |
MySQL | 开源型 | php,java,python等 | windows/liunx系统 | 中小型 |
PgSQL | 开源型 | php,java,python等 | windows/liunx系统 | 中小型 |
存储引擎
存储引擎 | 索引 | 事务 | 锁粒度 | 主要应用 | 忌用 |
---|---|---|---|---|---|
MyISAM | 支持 | 不支持 | 支持并发插入的表级锁 | select,insert高负载 | 读写并用 |
InnoDB | 支持 | 支持 | 支持MVCC的行级锁 | 事务处理 | 无 |
MEMORY | 支持 | 不支持 | 表锁 | 中间计算,静态数据 | 大型数据集,持久性存储 |
Archive | 不支持 | 不支持 | 行级锁 | 日志记录,聚合分析,只支持select,insert操作 | 随机读取,删除 |
字段选择
char vs varchar
char:适用于数据长度差不多一致,波动较小,最大数据长度小于50字节。
varchar:适用于数据长度变化较大,不能预知其具体长度的数据。
decimal vs float
decimal:用于存储精确数据,精度最高,但是占用空间很大。
float:用于存储非精确数据,会丢失数据精度,占用空间比 decimal 小。
int vs timestamp vs datetime
字段类型 | 存储空间 |
---|---|
TINYINT | 1 字节 |
SMALLINT | 2 字节 |
MEDIUMINY | 3 字节 |
INT | 4 字节 |
BINGINT | 8 字节 |
DATE | 3 字节 |
DATETIME | 8 字节 |
TIMESTAMP | 4 字节 |
CHAR(M) | M 字节,1 <= M <= 255 |
VARCHAR(M) | L+1 字节,在此 L <= M 和 1 <=M <= 255 |
FLOAT | 4 字节 |
DOUBLE | 8 字节 |
DECIMAL | 对 DECIMAL(M,D) ,如果 M>D,为 M+2 否则为 D+2 |
查询速度从快到慢如下:bigint > date > timestamp > time > datetime
主键与外键
主键是实体的高度抽象,主键与外键的配对,表示实体之间的连接
MySQL · 复制
简介
MySQL 的复制功能是构建基于大规模、高性能应用的基础,同时也是高可用性、可扩展性、灾难恢复、备份以及数据仓库等工作的基础。
复制解决的基本问题:是让一台服务器与其他服务器保持数据同步。
其他扩展功能:
- 数据分布:复制的开销很小,通常不会对带宽造成很大的压力,可以通过远程复制来构建不同的数据中心。
- 负载均衡:通过复制可以将读操作分布到多个服务器上,读写分离。
- 备份:对备份来说,复制是一项很有意义的技术补充,但复制既不是备份也不能够取代备份。
- 高可用性和故障切换:复制能避免 MySQL 单点失败,设计良好的故障切换系统能显著地缩短宕机时间。
复制原理
- 主库把数据更改记录到二进制日志(Binary Log)。
- 备库将主库的日志复制到自己的中继日志(Relay Log)。
- 备库读取中继日志中的事件,将其重放到备库数据之上。
复制的原理并不复杂,其实就是一个完全备份加上二进制日志备份的还原。需要特别注意的是,复制不是完全实时地进行同步,而是异步实时。
这种架构有一个缺陷:在主库上并发运行的查询在备库只能串行化执行,因为只有一个 SQL 线程来重放中继日志中的事件,这是很多工作负载的性能瓶颈所在。
复制方式
MySQL支持两种复制方式:基于行的复制(MySQL 5.1 版本支持)和基于语句的复制(MySQL 3.23 版本支持),这两种复制方式都是通过在主库上记录二进制日志、在备库重放日志的方式来实现异步的数据复制。
复制文件
mysql-bin.index
:二进制日志的索引文件,开启二进制日志时就会产生,该文件记录磁盘上的二进制日志文件,每一行包含了二进制文件的文件名。MySQL 依赖这个文件,除非在这个文件里有记录,否则识别不了二进制日志文件。
1 | [root@ptdsmapp04 mysql]# cat mysql-bin.index |
relay-log.index
:中继日志的索引文件,和 mysql-bin.index 作用类似。
1 | [root@ptdsmapp04 mysql]# cat relay-log.index |
master.info
:这个文件用于保存备库连接到主库所需要的信息,格式为纯文本(每行一个值)。此文件不能删除,否则备库在重启后无法连接到主库。这个文件以文本的方式记录了复制用户的密码,所以要注意此文件的权限控制。
1 | [root@ptdsmapp04 mysql]# cat master.info |
relay-log.info
:这个文件包含了当前备库复制的二进制日志和中继日志坐标,同样也不能删除这个文件,否则在备库重启后将无法从哪个位置开始复制,可能会导致重放已经执行过的语句。
1 | [root@ptdsmapp04 mysql]# cat relay-log.info |
参数详解
1 | mysql> show slave status\G |
1 | mysql> show master status\G |
变量 | 说明 |
---|---|
Slave_IO_State | 显示当前 IO 线程的状态,上述状态是等待主发送二进制日志 |
Master_Log_File | 显示当前同步的主上的二进制日志 |
Read_Master_Log_Pos | 显示当前同步的主上二进制日志的偏移量位置,单位是字节 |
Relay_Log_File | 显示当前写入的中继日志 |
Relay_Log_Pos | 显示当前执行到中继日志的偏移量位置 |
Relay_Master_Log_File | 当前中继日志同步的二进制日志 |
Slave_IO_Running | 从中 IO 线程的运行状态, YES 表示运行正常 |
Slave_SQL_Running | 从中 SQL 线程的运行状态, YES 表示运行正常 |
Exec_Master_Log_Pos | 表示同步的主上二进制日志偏移量的位置,Read_Master_Log_Pos - Exec_Master_Log_Pos 可以表示当前 SQL 线程运行的延时,单位是字节 |
Seconds_Behind_Master | 主从复制延迟,单位是秒 |
配置主从复制操作实践
- 每台服务器上创建复制用户
- 配置主库和备库
- 通知从库连接到主库并从主库复制数据
创建复制用户
主库和备库都创建该用户:
1 | GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'x.x.x.%' IDENTIFIED BY 'Changeme_123'; |
配置主库和从库
主库
my.cnf
文件
1 | server_id = 10 |
备库
my.cnf
文件
1 | server_id = 20 |
Tips:修改配置文件,需要重启 mysqld 服务生效
同步主从数据并启动复制
(1)克隆数据到从库
1 | 主库全局只读锁: |
(2)配置复制关系
1 | CHANGE MASTER TO |
(3)启动复制
1 | start slave; |
(4)检查复制线程
主库上可以看到由备库 I/O 线程向主库发起的连接:
1 | mysql> show processlist\G |
从库上也可以看到两个线程,一个是 I/O 线程,一个是 SQL 线程:
1 | mysql> show processlist\G |
(5)主库解锁
1 | unlock tables; |