2023年详解MySQL 开发规范
Posted 000X000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023年详解MySQL 开发规范相关的知识,希望对你有一定的参考价值。
数据库对象
数据库对象是数据库的组成部分,常见的有以下几种:表(Table )、索引(Index)、视图(View)、图表(Diagram)、缺省值(Default)、规则(Rule)、触发器(Trigger)、存储过程(Stored Procedure)、 用户(User)等。命名规范是指数据库对象如数据库(SCHEMA)、表(TABLE)、索引(INDEX)、约束(CONSTRAINTS)
等的命名约定。
数据库对象全局命名规范
-
1、命名使用具有意义的英文词汇,词汇中间以下划线分隔
-
2、命名只能使用英文字母、数字、下划线,以英文字母开头
-
3、避免用mysql的保留字如:backup、call、group等
-
4、所有数据库对象使用小写字母,实际上MySQL中是可以设置大小写是否敏感的,为了保证统一性,我们这边规范全部小写表示。
数据库命名规范
-
1、数据库命名尽量不超过30个字符。
-
2、数据库命名一般为项目名称+代表库含义的简写,比如IM项目的工作流数据库,可以是 im_flow。
-
3、数据库创建时必须添加默认字符集和校对规则子句。默认字符集为UTF8(已迁移dumbo的使用utf8mb4)
-
4、命名应使用小写。
表命名规范
-
1、常规表表名以t_开头,t代表table的意思,命名规则即 t + 模块(包含模块含义的简写)+ 表(包含表含义的简写),比如用户模块的教育信息表:t_user_eduinfo。
-
2、临时表(RD、QA或DBA同学用于数据临时处理的表),命名规则:temp前缀+模块+表+日期后缀:temp_user_eduinfo_20210719。
-
3、备份表(用于保存和归档历史数据或者作为灾备恢复的数据)命名规则,bak前缀+模块+表+日期后缀:bak_user_eduinfo_20210719。
-
4、同一个模块的表尽可能使用相同的前缀,表名称尽可能表达含义。
-
5、多个单词以下划线 _ 分隔。
-
6、常规表表名尽量不超过30个字符,temp表和bak表视情况而定,也尽量简短为宜,命名应使用小写。
字段命名规范
-
1、字段命名需要表示其实际含义的英文单词或简写,单词之间用下划线 _ 进行连接,如 service_ip、service_port。
-
2、各表之间相同意义的字段必须同名,比如a表和b表都有创建时间,应该统一为create_time,不一致会很混乱。
-
3、多个单词以下划线 _ 分隔
-
4、字段名尽量不超过30个字符,命名应该使用小写
索引命名规范
-
1、唯一索引使用uni + 字段名 来命名:
create unique index uni_uid on t_user_basic(uid)
。 -
2、非唯一索引使用idx + 字段名 来命名:
create index idx_uname_mobile on t_user_basic(uname,mobile)
。 -
3、多个单词以下划线 _ 分隔。
-
4、索引名尽量不超过50个字符,命名应该使用小写,组合索引的字段不宜太多,不然也不利于查询效率的提升。
-
5、多单词组成的列名,取尽可能代表意义的缩写,如 test_contact表member_id和friend_id上的组合索引:idx_mid_fid。
-
6、理解组合索引最左前缀原则,避免重复建设索引,如果建立了(a,b,c),相当于建立了(a), (a,b), (a,b,c)。
视图命名规范
-
1、视图名以v开头,表示view,完整结构是v+视图内容含义缩写。
-
2、如果视图只来源单个表,则为v+表名。如果视图由几个表关联产生就用v+下划线(_)连接几个表名,视图名尽量不超过30个字符。如超过30个字符则取简写。
-
3、如无特殊需要,严禁开发人员创建视图。
-
4、命名应使用小写。
存储过程命名规范
-
1、存储过程名以sp开头,表示存储过程(storage procedure)。之后多个单词以下划线(_)进行连接。存储过程命名中应体现其功能。存储过程名尽量不能超过30个字符。
-
2、存储过程中的输入参数以i_开头,输出参数以o_开头。
-
3、命名应使用小写。
create procedure sp_multi_param(in i_id bigint,in i_name varchar(32),out o_memo varchar(100))
函数命名规范
-
1、函数名以func开始,表示function。之后多个单词以下划线(_)进行连接,函数命名中应体现其功能。函数名尽量不超过30个字符。
-
2、命名应使用小写。
create function func_format_date(ctime datetime)
触发器命名规范
-
1、触发器以trig开头,表示trigger 触发器。
-
2、基本部分,描述触发器所加的表,触发器名尽量不超过30个字符。
-
3、后缀(_i,_u,_d),表示触发条件的触发方式(insert,update或delete)。
-
4、命名应使用小写。
DROP TRIGGER IF EXISTS trig_attach_log_d;
CREATE TRIGGER trig_attach_log_d AFTER DELETE ON t_dept FOR EACH ROW;
约束命名规范
-
1、唯一约束:uk_表名称_字段名。uk是UNIQUE KEY的缩写。比如给一个部门的部门名称加上唯一约束,来保证不重名,如下:
ALTER TABLE t_dept ADD CONSTRAINT un_name UNIQUE(name);
-
2、外键约束:fk_表名,后面紧跟该外键所在的表名和对应的主表名(不含t_)。子表名和父表名用下划线(_)分隔。如下:
ALTER TABLE t_user ADD CONSTRAINT fk_user_dept FOREIGN KEY(depno) REFERENCES t_dept (id);
-
3、非空约束:如无特殊需要,建议所有字段默认非空(not null),不同数据类型必须给出默认值(default)。
`id` int(11) NOT NULL,
`name` varchar(30) DEFAULT '',
`deptId` int(11) DEFAULT 0,
`salary` float DEFAULT NULL,
-
4、出于性能考虑,如无特殊需要,建议不使用外键。参照完整性由代码控制。这个也是我们普遍的做法,从程序角度进行完整性控制,但是如果不注意,也会产生脏数据。
-
5、命名应使用小写。
用户命名规范
-
1、生产使用的用户命名格式为 code_应用
-
2、只读用户命名规则为 read_应用
数据库对象设计规范
存储引擎的选择
-
1、如无特殊需求,必须使用innodb存储引擎。
可以通过 show variables like 'default_storage_engine'
来查看当前默认引擎。主要有MyISAM 和 InnoDB,从5.5版本开始默认使用 InnoDB 引擎。
基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行速度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。
字符集的选择
-
1、如无特殊要求,必须使用utf8或utf8mb4。
在国内,选择对中文和各语言支持都非常完善的utf8格式是最好的方式,MySQL在5.5之后增加utf8mb4编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。
所以utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。当然,为了节省空间,一般情况下使用utf8也就够了。
可以使用如下脚本来查看数据库的编码格式
SHOW VARIABLES WHERE Variable_name LIKE 'character_set_%' OR Variable_name LIKE 'collation%';
-- 或
SHOW VARIABLES Like '%char%';
表设计规范
-
1、不同应用间所对应的数据库表之间的关联应尽可能减少,不允许使用外键对表之间进行关联,确保组件对应的表之间的独立性,为系统或表结构的重构提供可能性。目前业内的做法一般 由程序控制参照完整性。
-
2、表设计的角度不应该针对整个系统进行数据库设计,而应该根据系统架构中组件划分,针对每个组件所处理的业务进行数据库设计。
-
3、表必须要有PK,主键的优势是唯一标识、有效引用、高效检索,所以一般情况下尽量有主键字段。
-
4、一个字段只表示一个含义。
-
5、表不应该有重复列。
-
6、禁止使用复杂数据类型(数组,自定义等),Json类型的使用视情况而定。
-
7、需要join的字段(连接键),数据类型必须保持绝对一致,避免隐式转换。比如关联的字段都是int类型。
-
8、设计应至少满足第三范式,尽量减少数据冗余。一些特殊场景允许反范式化设计,但在项目评审时需要对冗余字段的设计给出解释。
-
9、TEXT字段作为大体量文本存储,必须放在独立的表中 , 用PK与主表关联。如无特殊需要,禁止使用TEXT、BLOB字段。
-
10、需要定期删除(或者转移)过期数据的表,通过分表解决,我们的做法是按照2/8法则将操作频率较低的历史数据迁移到历史表中,按照时间或者则曾Id做切割点。
-
11、单表字段数不要太多,建议最多不要大于50个。过度的宽表对性能也是很大的影响。
-
12、MySQL在处理大表时,性能就开始明显降低,所以建议单表物理大小限制在16GB,表中数据行数控制在2000W内。
-
业内的规则是超过2000W性能开始明显降低。但是这个值是灵活的,你可以根据实际情况进行测试来判断,比如阿里的标准就是500W,百度的确是2000W。实际上是否宽表,单行数据所占用的空间都有起到作用的。
-
-
13、如果数据量或数据增长在前期规划时就较大,那么在设计评审时就应加入分表策略,数据拆分的做法:垂直拆分、水平拆分;
-
14、无特殊需求,严禁使用分区表
字段设计规范
-
1、INT:如无特殊需要,存放整型数字使用UNSIGNED INT型,整型字段后的数字代表显示长度。比如 id int(11) NOT NULL
-
2、DATETIME:所有需要精确到时间(时分秒)的字段均使用DATETIME,不要使用TIMESTAMP类型。
对于TIMESTAMP,它把写入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
另外DATETIME存储的范围也比较大:
timestamp所能存储的时间范围为:'1970-01-01 00:00:01.000000' 到 '2038-01-19 03:14:07.999999'。
datetime所能存储的时间范围为:'1000-01-01 00:00:00.000000' 到 '9999-12-31 23:59:59.999999'。
但是特殊情况,对于跨时区的业务,TIMESTAMP更为合适。
-
3、VARCHAR:所有动态长度字符串 全部使用VARCHAR类型,类似于状态等有限类别的字段,也使用可以比较明显表示出实际意义的字符串,而不应该使用INT之类的数字来代替;VARCHAR(N),
N表示的是字符数而不是字节数。比如VARCHAR(255),可以最大可存储255个字符(字符包括英文字母,汉字,特殊字符等)。但N应尽可能小,因为MySQL一个表中所有的VARCHAR字段最大长度是65535个字节,且存储字符个数由所选字符集决定。
如UTF8存储一个字符最大要3个字节,那么varchar在存放占用3个字节长度的字符时不应超过21845个字符。同时,在进行排序和创建临时表一类的内存操作时,会使用N的长度申请内存。(如无特殊需要,原则上单个varchar型字段不允许超过255个字符)
-
4、TEXT:仅仅当字符数量可能超过20000个的时候,才可以使用TEXT类型来存放字符类数据,因为所有MySQL数据库都会使用UTF8字符集。
所有使用TEXT类型的字段必须和原表进行分拆,与原表主键单独组成另外一个表进行存放,与大文本字段的隔离,目的是。如无特殊需要,不使用MEDIUMTEXT、TEXT、LONGTEXT类型
-
5、对于精确浮点型数据存储,需要使用DECIMAL,严禁使用FLOAT和DOUBLE。
-
6、如无特殊需要,尽量不使用BLOB类型
-
7、如无特殊需要,字段建议使用NOT NULL属性,可用默认值代替NULL
-
8、自增字段类型必须是整型且必须为UNSIGNED,推荐类型为INT或BIGINT,并且自增字段必须是主键或者主键的一部分。
索引设计规范
-
1、索引区分度
索引必须创建在索引选择性(区分度)较高的列上,选择性的计算方式为: selecttivity = count(distinct c_name)/count(*) ;
如果区分度结果小于0.2,则不建议在此列上创建索引,否则大概率会拖慢SQL执行
-
2、遵循最左前缀
对于确定需要组成组合索引的多个字段,设计时建议将选择性高的字段靠前放。使用时,组合索引的首字段,必须在where条件中,且需要按照最左前缀规则去匹配。
-
3、禁止使用外键,可以在程序级别来约束完整性
-
4、Text类型字段如果需要创建索引,必须使用前缀索引
-
5、单张表的索引数量理论上应控制在5个以内。经常有大批量插入、更新操作表,应尽量少建索引,索引建立的原则理论上是多读少写的场景。
-
6、ORDER BY,GROUP BY,DISTINCT的字段需要添加在索引的后面,形成覆盖索引
-
7、正确理解和计算索引字段的区分度,文中有计算规则,区分度高的索引,可以快速得定位数据,区分度太低,无法有效的利用索引,可能需要扫描大量数据页,和不使用索引没什么差别。
-
8、正确理解和计算前缀索引的字段长度,文中有判断规则,合适的长度要保证高的区分度和最恰当的索引存储容量,只有达到最佳状态,才是保证高效率的索引。
-
9、联合索引注意最左匹配原则:必须按照从左到右的顺序匹配,MySQL会一直向右匹配索引直到遇到范围查询(>、<、between、like)然后停止匹配。
如:depno=1 and empname>'' and job=1 如果建立(depno,empname,job)顺序的索引,job是用不到索引的。
-
10、应需而取策略,查询记录的时候,不要一上来就使用*,只取需要的数据,可能的话尽量只利用索引覆盖,可以减少回表操作,提升效率。
-
11、正确判断是否使用联合索引(上面联合索引的使用那一小节有说明判断规则),也可以进一步分析到索引下推(IPC),减少回表操作,提升效率。
-
12、避免索引失效的原则:禁止对索引字段使用函数、运算符操作,会使索引失效。这是实际上就是需要保证索引所对应字段的”干净度“。
-
13、避免非必要的类型转换,字符串字段使用数值进行比较的时候会导致索引无效。
-
14、模糊查询'%value%'会使索引无效,变为全表扫描,因为无法判断扫描的区间,但是'value%'是可以有效利用索引。
-
15、索引覆盖排序字段,这样可以减少排序步骤,提升查询效率
-
16、尽量的扩展索引,非必要不新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
举例子:比如一个品牌表,建立的的索引如下,一个主键索引,一个唯一索引
PRIMARY KEY (`id`),
UNIQUE KEY `uni_brand_define` (`app_id`,`define_id`)
当你同事业务代码中的检索语句如下的时候,应该立即警告了,即没有覆盖索引,也没按照最左前缀原则:
select brand_id,brand_name from ds_brand_system where status=? and define_id=? and app_id=?
建议改成如下:
select brand_id,brand_name from ds_brand_system where app_id=? and define_id=? and status=?
约束设计规范
-
1、PK应该是有序并且无意义的,由开发人员自定义,尽可能简短,并且是自增序列。
-
2、表中除PK以外,还存在唯一性约束的,可以在数据库中创建以“uk_”作为前缀的唯一约束索引。
-
3、PK字段不允许更新。
-
4、禁止创建外键约束,外键约束由程序控制。
-
5、如无特殊需要,所有字段必须添加非空约束,即not null。
-
6、如无特殊需要,所有字段必须有默认值。
SQL使用规范
select 检索的规范性
-
1、尽量避免使用
select *,join
语句使用select *
可能导致只需要访问索引即可完成的查询需要回表取数。 -
一种是可能取出很多不需要的数据,对于宽表来说,这是灾难;一种是尽可能避免回表,因为取一些根本不需要的数据而回表导致性能低下,是很不合算。
-
2、严禁使用
select * from t_name
,而不加任何where条件,道理一样,这样会变成全表全字段扫描。 -
3、MySQL中的text类型字段存储:
-
3.1、不与其他普通字段存放在一起,因为读取效率低,也会影响其他轻量字段存取效率。
-
3.2、如果不需要text类型字段,又使用了select *,会让该执行消耗大量io,效率也很低下
-
-
4、在取出字段上可以使用相关函数,但应尽可能避免出现 now() , rand() , sysdate() 等不确定结果的函数,在Where条件中的过滤条件字段上严禁使用任何函数,包括数据类型转换函数。大量的计算和转换会造成效率低下,这个在索引那边也描述过了。
-
5、分页查询语句全部都需要带有排序条件 , 否则很容易引起乱序
-
6、用in()/union替换or,效率会好一些,并注意in的个数小于300
-
7、严禁使用%前缀进行模糊前缀查询:如:
select a,b,c from t_name where a like ‘%name’;
可以使用%模糊后缀查询如:select a,b from t_name where a like ‘name%’;
-
8、避免使用子查询,可以把子查询优化为join操作
通常子查询在in子句中,且子查询中为简单SQL(不包含union、group by、order by、limit从句)时,才可以把子查询转化为关联查询进行优化。
子查询性能差的原因:
-
子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能 会受到一定的影响;
-
特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大;
-
由于子查询会产生大量的临时表也没有索引,所以会消耗过多的CPU和IO资源,产生大量的慢查询。
操作的规范性
-
1、禁止使用不含字段列表的INSERT语句
如:insert into values ('a','b','c'); 应使用 insert into t_name(c1,c2,c3) values ('a','b','c'); 。
-
2、大批量写操作(
UPDATE、DELETE、INSERT
),需要分批多次进行操作-
大批量操作可能会造成严重的主从延迟,特别是主从模式下,大批量操作可能会造成严重的主从延迟,因为需要slave从master的binlog中读取日志来进行数据同步。
-
binlog日志为row格式时会产生大量的日志
-
数据库开发规范
数据库开发规范
数据库开发规范定义:开发规范是针对内部开发的一系列建议或规则。
开发规范本身也包含几部分:基本命名和约束规范,字段设计规范,索引规范,使用规范。
规范存在意义
-
保证线上数据库schema规范
-
减少出问题概率
-
方便自动化管理
-
规范需要长期坚持,对开发和DBA是一个双赢的事情
想想没有开发规范,有的开发写出各种全表扫描的SQL语句或者各种奇葩SQL语句,我们之前就看过开发写的SQL 可以打印出好几页纸。这种造成业务本身不稳定,也会让DBA天天忙于各种救火。
基本命名和约束规范
-
表字符集选择UTF8 ,如果需要存储emoj表情,需要使用UTF8mb4(强烈建议直接使用UTF8mb4, 因为其可以保存4个字节,而utf-8编码只能保存最大3个字节)
-
InnoDB和MyISAM是许多人在使用MySQL时最常用的两个存储类型,这两个表类型各有优劣,视具体应用而定。基本的差别为:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持以及外部键等高级数据库功能。
-
变长字符串尽量使用varchar varbinary
-
不在数据库中存储图片、文件等(应该存储其路径)
-
单表数据量控制在1亿以下
-
库名、表名、字段名不使用保留字
-
库名、表名、字段名、索引名使用小写字母,以下划线分割 ,需要见名知意
-
库表名不要设计过长,尽可能用最少的字符表达出表的用途
字段规范
-
所有字段均定义为NOT NULL ,除非你真的想存Null
-
字段类型在满足需求条件下越小越好,使用UNSIGNED存储非负整数 ,实际使用时候存储负数场景不多
-
使用TIMESTAMP存储时间(timestamp比datetime更省空间,但时间范围较小)
-
使用varchar存储变长字符串 ,当然要注意varchar(M)里的M指的是字符数不是字节数;使用UNSIGNED INT存储IPv4 地址而不是CHAR(15) ,这种方式只能存储IPv4,存储不了IPv6
-
使用DECIMAL存储精确浮点数,用float有的时候会有问题
-
少用blob text
关于为什么定义不使用Null的原因
* 1.浪费存储空间,因为InnoDB需要有额外一个字节存储
* 2.表内默认值Null过多会影响优化器选择执行计划
关于使用datatime和timestamp,现在在5.6.4之后又有了变化,使用二者存储在存储空间上大差距越来越小 ,并且本身datatime存储范围就比timestamp大很多,timestamp只能存储到2038年
索引规范
-
单个索引字段数不超过5,单表索引数量不超过5,索引设计遵循B+ Tree索引最左前缀匹配原则
-
选择区分度高的列作为索引
-
建立的索引能覆盖80%主要的查询,不求全,解决问题的主要矛盾
-
DML和order by和group by字段要建立合适的索引
-
避免索引的隐式转换
-
避免冗余索引
关于索引规范,一定要记住索引这个东西是一把双刃剑,在加速读的同时也引入了很多额外的写入和锁,降低写入能力,这也是为什么要控制索引数原因。之前看到过不少人给表里每个字段都建了索引,其实对查询可能起不到什么作用。
冗余索引例子
-
idx_abc(a,b,c)
-
idx_a(a) 冗余
-
idx_ab(a,b) 冗余
隐式转换例子
字段:remark
varchar(50) NOT Null
MySQL>SELECT id
, gift_code
FROM gift WHERE deal_id
= 640 AND remark=115127; 1 row in set (0.14 sec)
MySQL>SELECT id
, gift_code
FROM pool_gift WHEREdeal_id
= 640 AND remark=‘115127’; 1 row in set (0.005 sec)
字段定义为varchar,但传入的值是个int,就会导致全表扫描,要求程序端要做好类型检查
SQL类规范
-
尽量不使用存储过程、触发器、函数等
-
避免使用大表的JOIN,MySQL优化器对join优化策略过于简单
-
避免在数据库中进行数学运算和其他大量计算任务
-
SQL合并,主要是指的DML时候多个value合并,减少和数据库交互
-
合理的分页,尤其大分页
-
UPDATE、DELETE语句不使用LIMIT ,容易造成主从不一致
数据库运维规范
运维规范主要内容
-
SQL审核,DDL审核和操作时间,尤其是OnlineDDL
-
高危操作检查,Drop前做好数据备份
-
权限控制和审计
-
日志分析,主要是指的MySQL慢日志和错误日志
-
高可用方案
-
数据备份方案
版本选择
-
MySQL社区版,用户群体最大
-
MySQL企业版,收费
-
Percona Server版,新特性多
-
MariaDB版,国内用户不多
建议选择优先级为:MySQL社区版 > Percona Server > MariaDB > MySQL 企业版
不过现在如果大家使用RDS服务,基本还以社区版为主
Online DDL问题
原生MySQL执行DDL时需要锁表,且锁表期间业务是无法写入数据的,对服务影响很大,MySQL对这方面的支持是比较差的。大表做DDL对DBA来说是很痛苦的,相信很多人经历过。如何做到Online DDL呢,是不是就无解了呢?当然不是!
上面表格里提到的 Facebook OSC和5.6 OSC也是目前两种比较靠谱的方案
MySQL 5.6的OSC方案还是解决不了DDL的时候到从库延时的问题,所以现在建议使用Facebook OSC这种思路更优雅
下图是Facebook OSC的思路
后来Percona公司根据Facebook OSC思路,用perl重写了一版,就是我们现在用得很多的pt-online-schema-change,软件本身非常成熟,支持目前主流版本。
使用pt-online-schema-change的优点有:
-
1.无阻塞写入
-
2.完善的条件检测和延时负载策略控制
值得一提的是,腾讯互娱的DBA在内部分支上也实现了Online DDL,之前测试过确实不错,速度快,原理是通过修改InnoDB存储格式来实现。
使用pt-online-schema-change的限制有:
-
改表时间会比较长(相比直接alter table改表)
-
修改的表需要有唯一键或主键
-
在同一端口上的并发修改不能太多
可用性
关于可用性,我们今天分享一种无缝切主库方案,可以用于日常切换,使用思路也比较简单
在正常条件下如何无缝去做主库切换,核心思路是让新主库和从库停在相同位置,主要依赖slave start until 语句,结合双主结构,考虑自增问题。
MySQL集群方案:
-
集群方案主要是如何组织MySQL实例的方案
-
主流方案核心依然采用的是MySQL原生的复制方案
-
原生主从同步肯定存在着性能和安全性问题
MySQL半同步复制:
现在也有一些理论上可用性更高的其它方案
-
Percona XtraDB Cluster(没有足够的把控力度,不建议上)
-
MySQL Cluster(有官方支持,不过实际用的不多)
红框内是目前大家使用比较多的部署结构和方案。当然异常层面的HA也有很多第三方工具支持,比如MHA、MMM等,推荐使用MHA
sharding拆分问题
-
Sharding is very complex, so it?s best not to shard until it?s obvious that you will actually need to!
-
sharding是按照一定规则数据重新分布的方式
-
主要解决单机写入压力过大和容量问题
-
主要有垂直拆分和水平拆分两种方式
-
拆分要适度,切勿过度拆分
-
有中间层控制拆分逻辑最好,否则拆分过细管理成本会很高
曾经管理的单表最大60亿+,单表数据文件大小1TB+,人有时候就要懒一些
上图是水平拆分和垂直拆分的示意图
数据库备份
首先要保证的,最核心的是数据库数据安全性。数据安全都保障不了的情况下谈其他的指标(如性能等),其实意义就不大了。
备份的意义是什么呢?
-
数据恢复!
-
数据恢复!
-
数据恢复!
目前备份方式的几个纬度:
-
全量备份 VS 增量备份
-
热备 VS 冷备
-
物理备份 VS 逻辑备份
-
延时备份
-
全量binlog备份
建议方式:
-
热备+物理备份
-
核心业务:延时备份+逻辑备份
-
全量binlog备份
借用一下某大型互联网公司做的备份系统数据:一年7000+次扩容,一年12+次数据恢复,日志量每天3TB,数据总量2PB,每天备份数据量百TB级,全年备份36万次,备份成功了99.9%。
主要做的几点:
-
备份策略集中式调度管理
-
xtrabackup热备
-
备份结果统计分析
-
备份数据一致性校验
-
采用分布式文件系统存储备份
备份系统采用分布式文件系统原因:
-
解决存储分配的问题
-
解决存储NFS备份效率低下问题
-
存储集中式管理
-
数据可靠性更好
使用分布式文件系统优化点:
* Pbzip压缩,降低多副本存储带来的存储成本,降低网络带宽消耗
* 元数据节点HA,提高备份集群的可用性
* erasure code方案调研
数据恢复方案
目前的MySQL数据恢复方案主要还是基于备份来恢复,可见备份的重要性。比如我今天下午15点删除了线上一张表,该如何恢复呢?首先确认删除语句,然后用备份扩容实例启动,假设备份时间点是凌晨3点,就还需要把凌晨3点到现在关于这个表的binlog导出来,然后应用到新扩容的实例上,确认好恢复的时间点,然后把删除表的数据导出来应用到线上。
性能优化
复制优化
MySQL复制:
-
是MySQL应用得最普遍的应用技术,扩展成本低
-
逻辑复制
-
单线程问题,从库延时问题
-
可以做备份或读复制
问题很多,但是能解决基本问题
上图是MySQL复制原理图,红框内就是MySQL一直被人诟病的单线程问题
单线程问题也是MySQL主从延时的一个重要原因,单线程解决方案:
-
官方5.6+多线程方案
-
Tungsten为代表的第三方并行复制工具
-
sharding
上图是MySQL5.6 目前实现的并行复制原理图,是基于库级别的复制,所以如果你只有一个库,使用这个意义不大
当然MySQL也认识到5.6这种并行的瓶颈所在,所以在5.7引入了另外一种并行复制方式,基于logical timestamp的并行复制,并行复制不再受限于库的个数,效率会大大提升
上图是5.7的logical timestamp的复制原理图
刚才我也提到MySQL原来只支持异步复制,这种数据安全性是非常差的,所以后来引入了半同步复制,从5.5开始支持
上图是原生异步复制和半同步复制的区别。可以看到半同步通过从库返回ACK这种方式确认从库收到数据,数据安全性大大提高
在5.7之后,半同步也可以配置你指定多个从库参与半同步复制,之前版本都是默认一个从库
对于半同步复制效率问题有一个小的优化,就是使用5.6+的mysqlbinlog以daemon方式作为从库,同步效率会好很多
关于更安全的复制,MySQL 5.7也是有方案的,方案名叫Group replication 官方多主方案,基于Corosync实现
主从延时问题
原因:一般都会做读写分离,其实从库压力反而比主库大/从库读写压力大非常容易导致延时。
解决方案:
-
首先定位延时瓶颈
-
如果是IO压力,可以通过升级硬件解决,比如替换SSD等
-
如果IO和CPU都不是瓶颈,非常有可能是SQL单线程问题,解决方案可以考虑刚才提到的并行复制方案
-
如果还有问题,可以考虑sharding拆分方案
提到延时不得不提到很坑人的Seconds behind master,使用过MySQL的应该很熟悉
这个值的源码里算法
long time_diff= ((long)(time(0) – mi->rli.last_master_timestamp) – mi->clock_diff_with_master);
Secondsbehindmaster来判断延时不可靠,在网络抖动或者一些特殊参数配置情况下,会造成这个值是0但其实延时很大了。通过heartbeat表插入时间戳这种机制判断延时是更靠谱的
复制注意点:
-
Binlog格式,建议都采用row格式,数据一致性更好
-
Replication filter应用
主从数据一致性问题:
-
row格式下的数据恢复问题
InnoDB优化
成熟开源事务存储引擎,支持ACID,支持事务四个隔离级别,更好的数据安全性,高性能高并发,MVCC,细粒度锁,支持O_DIRECT。
主要优化参数:
-
innodbfileper_table =1
-
innodbbufferpool_size,根据数据量和内存合理设置
-
innodbflushlog_attrxcommit= 0 1 2
-
innodblogfile_size,可以设置大一些
-
innodbpagesize
-
Innodbflushmethod = o_direct
-
innodbundodirectory 放到高速设备(5.6+)
-
innodbbufferpool_dump
-
atshutdown ,bufferpool dump (5.6+)
上图是5.5 4G的redo log和5.6 设置大于4G redo log文件性能对比,可以看到稳定性更好了。innodblogfile_size设置还是很有意义的
InnoDB比较好的特性:
-
Bufferpool预热和动态调整大小,动态调整大小需要5.7支持
-
Page size自定义调整,适应目前硬件
-
InnoDB压缩,大大降低数据容量,一般可以压缩50%,节省存储空间和IO,用CPU换空间
-
Transportable tablespaces,迁移ibd文件,用于快速单表恢复
-
Memcached API,full text,GIS等
InnoDB在SSD上的优化:
-
在5.5以上,提高innodbwriteiothreads和innodbreadiothreads
-
innodbiocapacity需要调大
-
日志文件和redo放到机械硬盘,undo放到SSD,建议这样,但必要性不大
-
atomic write,不需要Double Write Buffer
-
InnoDB压缩
-
单机多实例
也要搞清楚InnoDB哪些文件是顺序读写,哪些是随机读写
随机读写:
-
datadir
-
innodbdata file_path
-
innodbundo directory
顺序读写:
-
innodbloggrouphomedir
-
log-bin
InnoDB VS MyISAM:
-
数据安全性至关重要,InnoDB完胜,曾经遇到过一次90G的MyISAM表repair,花了两天时间,如果在线上几乎不可忍受
-
并发度高
-
MySQL 5.5默认引擎改为InnoDB,标志着MyISAM时代的落幕
TokuDB:
-
支持事务 ACID 特性,支持多版本控制(MVCC)
-
基于Fractal Tree Index,非常适合写入密集场景
-
高压缩比,原生支持Online DDL
-
主流分支都支持,收费转开源 。目前可以和InnoDB媲美的存储引擎
目前主流使用TokuDB主要是看中了它的高压缩比,Tokudb有三种压缩方式:quicklz、zlib、lzma,压缩比依次更高。现在很多使用zabbix的后端数据表都采用的TokuDB,写入性能好,压缩比高。
下图是我之前做的测试对比和InnoD
上图是sysbench测试的和InnoDB性能对比图,可以看到TokuDB在测试过程中写入稳定性是非常好的。
tokudb存在的问题:
-
官方分支还没很好的支持
-
热备方案问题,目前只有企业版才有
-
还是有bug的,版本更新比较快,不建议在核心业务上用
比如我们之前遇到过一个问题:TokuDB的内部状态显示上一次完成的checkpoint时间是“Jul 17 12:04:11 2014”,距离当时发现现在都快5个月了,结果堆积了大量redo log不能删除,后来只能重启实例,结果重启还花了七八个小时
MySQL优化相关的case
Query cache,MySQL内置的查询加速缓存,理念是好的,但设计不够合理,有点out。
锁的粒度非常大MySQL 5.6默认已经关闭
When the query cache helps, it can help a lot. When it hurts, it can hurt a lot.明显前半句已经没有太大用处,在高并发下非常容易遇到瓶颈。
关于事务隔离级别 ,InnoDB默认隔离级别是可重复读级别,当然InnoDB虽然是设置的可重复读,但是也是解决了幻读的,建议改成读已提交级别,可以满足大多数场景需求,有利于更高的并发,修改transaction-isolation。
上图是一个比较经典的死锁case,有兴趣可以测试下
关于SSD
关于SSD,还是提一下吧。某知名大V说过“最近10年对数据库性能影响最大的是闪存”,稳定性和性能可靠性已经得到大规模验证,多块SATA SSD做Raid5,推荐使用。采用PCIe SSD,主流云平台都提供SSD云硬盘支持。
最后说一下大家关注的单表60亿记录问题,表里数据也是线上比较核心的。
先说下当时情况,表结构比较简单,都是bigint这种整型,索引比较多,应该有2-3个,单表行数60亿+,单表容量1.2TB左右,当然内部肯定是有碎片的。
形成原因:历史遗留问题,按照我们前面讲的开发规范,这个应该早拆分了,当然不拆有几个原因:
-
性能未遇到瓶颈 ,主要原因
-
DBA比较“懒“
-
想看看InnoDB的极限,挑战一下。不过风险也是很大的,想想如果在一个1.2TB表上加个字段加个索引,那感觉绝对酸爽。还有就是单表恢复的问题,恢复时间不可控。
我们后续做的优化 ,采用了刚才提到的TokuDB,单表容量在InnoDB下1TB+,使用Tokudb的lzma压缩到80GB,压缩效果非常好。这样也解决了单表过大恢复时间问题,也支持online DDL,基本达到我们预期。
今天讲的主要针对MySQL本身优化和规范性质的东西,还有一些比较好的运维经验,希望大家能有所收获。今天这些内容是为后续数据库做平台化的基础。我今天分享就到这里,谢谢大家。
QA
Q1:use schema;select * from table; 和select * from schema.table;两种写法有什么不一样吗?会对主从同步有影响吗?
对于主从复制来说执行效率上差别不大,不过在使用replication filter时候这种情况需要小心,应该要使用ReplicateWildIgnoreTable这种参数,如果不使用带wildignore,第一种方式会有问题,过滤不起作用。
Q2:对于用于MySQL的ssd,测试方式和ssd的参数配置方面,有没有好的建议?主要针对ssd的配置哈
关于SATA SSD配置参数,建议使用Raid5,想更保险使用Raid50,更土豪使用Raid 10
上图是主要的参数优化,性能提升最大的是第一个修改调度算法的
Q3:数据库规范已制定好,如何保证开发人员必须按照规范来开发?
关于数据库规范实施问题,也是有两个方面吧,第一、定期给开发培训开发规范,让开发能更了解。第二、还是在流程上规范,比如把我们日常通用的建表和字段策略固化到程序,做成自动化审核系统。这两方面结合 效果会比较好。
Q4:如何最大限度提高innodb的命中率?
这个问题前提是你的数据要有热点,读写热点要有交集,否则命中率很难提高。在有热点的前提下,也要求你的你的内存要足够大,能够存更多的热点数据。尽量不要做一些可能污染bufferpool的操作,比如全表扫描这种。
Q5:主从复制的情况下,如果有CAS这样的需求,是不是只能强制连主库?因为有延迟的存在,如果读写不在一起的话,会有脏数据。
如果有CAS需求,确实还是直接读主库好一些,因为异步复制还是会有延迟的。只要SQL优化的比较好,读写都在主库也是没什么问题的。
Q6:关于开发规范,是否有必要买国标?
这个国标是什么东西,不太了解。不过从字面看,国标应该也是偏学术方面的,在具体工程实施时候未必能用好。
Q7:主从集群能不能再细化一点那?不知道这样问合适不?
看具体哪方面吧。主从集群每个小集群一般都是采用一主多从方式,每个小集群对应特定的一组业务。然后监控备份和HA都是在每个小集群实现。
Q8:如何跟踪数据库table某个字段值发生变化?
追踪字段值变化可以通过分析row格式binlog好一些。比如以前同事就是通过自己开发的工具来解析row格式binlog,跟踪数据行变化。
Q9:对超大表水平拆分,在使用MySQL中间件方面有什么建议和经验分享?
对于超大表水平拆分,在中间件上经验不是很多,早期人肉搞过几次。也使用过自己研发的数据库中间件,不过线上应用的规模不大。关于目前众多的开源中间件里,360的atlas是目前还不错的,他们公司内部应用的比较多。
Q10:我们用的MySQL proxy做读负载,但是少量数据压力下并没有负载,请问有这回事吗?
少量数据压力下,并没有负载 ,这个没测试过,不好评价
Q11:对于binlog格式,为什么只推荐row,而不用网上大部分文章推荐的Mix ?
这个主要是考虑数据复制的可靠性,row更好。mixed含义是指如果有一些容易导致主从不一致的SQL ,比如包含UUID函数的这种,转换为row。既然要革命,就搞的彻底一些。这种mix的中间状态最坑人了。
Q12: 读写分离,一般是在程序里做,还是用proxy ,用proxy的话一般用哪个?
这个还是独立写程序好一些,与程序解耦方便后期维护。proxy国内目前开源的比较多,选择也要慎重。
Q13: 我想问一下关于mysql线程池相关的问题,什么情况下适合使用线程池,相关的参数应该如何配置,老师有这方面的最佳实践没有?
线程池这个我也没测试过。从原理上来说,短链接更适合用线程池方式,减少建立连接的消耗。这个方面的最佳配置,我还没测试过,后面测试有进展可以再聊聊。
Q14: 误删数据这种,数据恢复流程是怎么样的(从库也被同步删除的情况)?
看你删除数据的情况,如果只是一张表,单表在几GB或几十GB。如果能有延时备份,对于数据恢复速度是很有好处的。恢复流程可以参考我刚才分享的部分。目前的MySQL数据恢复方案主要还是基于备份来恢复 ,可见备份的重要性。比如我今天下午15点删除了线上一张表,该如何恢复呢。首先确认删除语句,然后用备份扩容实例启动,假设备份时间点是凌晨3点。就还需要把凌晨3点到现在关于这个表的binlog导出来,然后应用到新扩容的实例上。确认好恢复的时间点,然后把删除表的数据导出来应用到线上。
Q15: 关于备份,binlog备份自然不用说了,物理备份有很多方式,有没有推荐的一种,逻辑备份在量大的时候恢复速度比较慢,一般用在什么场景?
物理备份采用xtrabackup热备方案比较好。逻辑备份一般用在单表恢复效果会非常好。比如你删了一个2G表,但你总数据量2T,用物理备份就会要慢了,逻辑备份就非常有用了。
在原博客基础上修改。
以上是关于2023年详解MySQL 开发规范的主要内容,如果未能解决你的问题,请参考以下文章