本次学习是根据网络课程《打造扛得住的MySQL数据库架构》
于2020年02月学习
本人建议如果有兴趣学习这门网络课程,因先有基础的mysql数据库的一些知识,至少你会用它。我认为本篇网络课程中,拓展知识为主,并非专门研究某一个细的点,本门网络课程是一门讲解和经验讨论的课程,如果想学习相应的专门的模块,还需自行阅读相关资料。
加之本人这篇博文更像学习笔记,并非常规的研究型博文。
以上是本人的见解,并非准确,参考即可,若有不妥请点明。
目录
前言:
一、影响服务器的几个因素
二、mysql服务器参数
三、数据库结构设计
四、数据库复制功能
五、数据库结构设计
六、索引
七、sql查询优化
八、数据库分库分表
九、数据库监控
硬件问题:
确定可以使用的内存的上限
确定MySQL的每个连接使用的内存
sort_buffer_size 排序缓存区内存大小,不宜过大,mysql是固定给每次新的都增加这么多内存,连接数过大容易溢出崩溃
join_buffer_size 连接缓冲区的尺寸,每个连接可能出现多个缓存,
read_buffer_size mysam表全表扫描时创建,
read_rnd_buffer_size 索引缓冲区的大小,
确定需要为操作系统保留多少内存
如何为缓存池分配内存
innodb_buffer_pool_size :如果只用innodb表,那么可以使用以下方程:总内存-(每个线程所需要的内存*连接数)-系统保留内存 (标准:服务器内存的75%以上)
key_buffer_size : mysam索引缓存
innodb I/O相关配置
innodb_log_file_size:单个恢复日志大小
innodb_log_file_in_group:
innodb_log_buffer_size:日志缓冲区32MB-128MB就足够
innodb_flush_log_at_trx_commit:
0:每秒进行一次log写入cache,并flush log到磁盘
1:默认操作,在每次事务提交执行log写入cache,并flush log到磁盘
2:建议,每次事务提交,执行log数据写入到cache,每秒执行一次flush log到磁盘
innodb_flush_method=O_DIRECT (linux建议)
innodb_file_per_table = 1 (如何使用表空间,1就是每个都建立)
innodb_doublewrite = 1 (应设置,避免发生数据安全问题)
MyISAM:
delay_key_write
off
on
all
expire_logs_day :指定自动清理binlog的天数(保存7天左右)
max_allowed_packet : 控制mysql可以接受包的大小
skip_name_resolve: 禁用DNS查找
sysdate_is_now 确保sysdate()返回确定性日期
read_only 禁止非super权限的用户写权限
skip_slave_start : 禁用Slave自动恢复
sql_mode :设置MySQL所使用的SQL模式(谨慎休息)
strict_trans_tables
no_engine_subtiyution
no_zero_date
no_zero_in_date
only_full_group_by
sync_binlog 控制MySQL如何向磁盘刷新binlog
tmp_table_size和max_heap_table_size 控制内存临时表大小,设置一致
max_connections 控制允许的最大连接数(可以来考虑设置值 1000)
过分的反范式化为表建立太多的列
过分的范式化造成太多的表关联
在OLTP环境中使用不恰当的分区表(并不是分库分表)
使用外键保证数据的完整性
数据库优化的优先级:
数据库结构设计和sql语句
数据库存储引擎的选择和参数配置
系统选择及优化
硬件升级
基准测试(简化压力测试)
建立MySQL服务器的性能基准线
模拟比当前系统更高的负载,以找出系统的扩展瓶颈
测试不同的硬件、软件和操作系统配置
TPS:单位时间内所处理的事务数
QPS单位时间内所处理的查询数
响应时间
平均响应时间、最小响应时间、最大响应时间、各时间所占百分比
并发量:同时处理的查询请求的数量(不等同于web并发量)
自带的mysqlslaps
sysBench
压力测试
减少数据冗余
尽量避免数据维护中出现更新,插入和删除异常
插入异常:如果表中的某个实体随着另一个实体而存在
更新异常:如果更改表中的某个实体的单独属性时,需要对多行进行更新
删除异常:如果删除表中的某一个实体则会导致其他实体的消失
节约数据存储空间
提高查询效率
需求分析
逻辑设计
物理设计
定义数据库、表及字段的命名规范
选择合适的存储引擎
为表中字段选择合适的数据类型
优先:数字类型->日期或二进制类型->字符
优化:空间小的数据类型
varchar和char
日期类型
datatime
timestamp(有一定范围内),空间小;(和时区有关)
date 存储日期部分,占用空间少,进行日期函数计算
time 存储时间部分,
注意事项
不要使用字符串类型来存储日期时间数据
日期时间类型通常比字符串占用的存储空间小
日期时间类型进行查找过滤时可以利用日期来进行对比
日期时间类型还有着丰富的处理函数,可以方便的对时期类型进行日期计算
使用int存储日期时间不如使用timestamp类型
建立表的结构
第一范式
数据库表中的所有字段都只具有单一属性
单一属性的列是由基本的数据类型所构成
设计出来的表都是简单的二维表
第二范式
要求一个表中只具有一个业务主键,也就是说符合第二范式的表中不能存在非逐渐列对只对部分逐渐的依赖关系
第三范式
指每一个非主属性既不部分依赖也不传递依赖于业务主键,也就是在第二范式的基础上消除了非主属性对主键的传递依赖
范式化利好于写,难以维护索引
反范式化利于查,便于索引维护
二进制日志
基于段的日志格式 binlog_format = STATEMENT (SBR)
基于行的日志格式 binlog_format = ROW(默认) (RBR)
使mysql主从复制更加安全
但是消耗磁盘io,存量大
附加操作:binlog_row_image = [FULL\MINIMAL\NOBLOB]
混合日志格式 binlog_format = MIXED
根据sql语句由系统决定在基于段和行的日志格式中进行选择
数据量大小由所执行的sql语句决定
建议使用row格式为主,建议binlog_row_image为minimal
日志格式对于复制的影响
配置mysql复制
基于日志点的复制
设定相应的复制账号并授权
配置主服务器(配置文件)
bin_log = mysql-bin
server_id = 100
配置从服务器
bin_log = mysql-bin
server-id = 101
relay_log = mysql-relay-bin
log_slave-update = on [可选]
read_only = on [可选]最好选择配置
初始化从服务器
mysqldump -uroot -p --single-transaction --master-data=2 --triggers --routines --all-databases >>all2.sql (可能存在很大的阻塞)
xtrabackup --slave-info (非innodb表会锁表,innodb不会锁表)推荐使用
拷贝 mysql -uroot -p < xxx.sql
启动复制链路(在从服务器上)
change master to master_host='master_host_ip',master_user='repl',master_password='password',master_log_file='mysql_log_file_name',master_log_pos=4;
start slave;
优点:
是MySQL最早支持的复制技术,bug相对较少
对sql查询没有任何限制
故障处理比较容易
缺点:
故障转移时重新获取新主的日志点信息比较困难
基于GTID复制
mysql5.6开始支持
全局事务id,其保证为每一个在主上提交的事务在复制集群中可以生成一个唯一的id
在主DB服务器上建立复制账号
配置主服务器(配置文件)
bin_log = /*/mysql-bin
server_id = 100
gtid_mode = on (关键参数)
enforce-gtid-consistency = on
log-slave-updates = on
配置从服务器
server-id = 101
relay_log = /*/relay_log
gtid_mode = on
enforce-gtid-consistency = on
log_slave-update = on
read_only = on [可选]最好选择配置
master_info_repository = TABLE[建议]
raley_log_info_repository = TABLE[建议]
初始化服务器数据
启动GTID的复制
change master to master_host='master_host_ip',master_user='repl',master_password='password',MASTER_AUTO_POSITION = 1, get_master_public_key=1;(mysql8中需要加最后一个,是密码的问题;)
start slave;
优点:
可以很方便的进行故障转移
从库不会丢失主库上的任何修改
缺点:
故障处理比较复杂
对执行的sql有一定的限制
如何选择复制模式
所使用的MySQL版本(5.6以后的版本适合GTID)
复制架构及主从切换的方式
所使用的高可用管理组件
对应用的支持程序(GTID对sql的限制)
mysql复制拓扑
mysql5.7之前只能一个主库,以后可以多个主库
一主多从复制拓扑
配置简单
可以用多个库分担读负载
为不同业务使用不同的从库(例如前后台查询)
将一台从库放到远程IDC,用作灾备恢复
分担主库读负载
主-主复制拓扑
注意事项
产生数据冲突而造成复制链路的中路
耗费大量时间
造成数据丢失
两个主中所操作的表最好分开
使用下面两个参数控制自增ID的生成
auto_increment_increment = 2 (自增id 的间隔)
auto_increment_offset = 1|2 (自增id的生成基数)
主备模式复制拓扑
注意事项
确保两台服务器上的初始数据相同
确保两台服务器上已经binlog并且有不同的server_id
在两台服务器上启用log_slave_updates参数
在初始的备库上启用read_only
级联复制
mysql复制性能优化
影响的因素:
主库写入二进制日志的时间
控制主库的事务大小,分割大事务
二进制日志传输的时间
使用mixed日志格
设置 binlog_row_image=minimal
默认情况下从只有一个sql线程,主上并发的修改在从上变成可串行
使用多线程复制(在mysql5.7中可以逻辑时期)
stop slave;
set global slave_parallel_type='logical_clock';
set global slave_parallel_worker=4;
set slave;
mysql复制常见问题处理
由于数据损坏或丢失所引起的主从复制错误
主库上的二进制日志损坏
备库上的中继日志损坏
在从库上进行数据修改造成的主从复制错误
不唯一的server_id或server_uuid
max_allow_packet设置引起的主从复制错误
无法分担主数据库的写负载
分库分表
无法自动进行故障转移及主从切换
无法提供读写分离功能
MMM架构
优点:
使用perl脚本语言开发及完全开源
提供了读写vip(虚拟ip),使服务器角色的变更对前端应用透明
提供了从服务的延迟监控
提供了主数据库故障转移后从服务器对新主的重新同步功能
很容易对发生故障的主数据库重新上线
提供了从服务器监控
缺点:
发布时间比较早不支持MySQL新的复制功能(GTID复制功能不支持,多线程复制不支持)
没有读负载均衡的功能
在进行主从切换时,容易造成数据丢失
MMM监控服务存在单点故障
MHA架构
现在主从上建立好gtid链路
建立ssh免密登录
下载MHA node安装包
安装perl的相关软件包
安装MHA node软件包
配置MHA
只需要在管理点配置即可
server default
user=mha
password=123456
manager_workdir=/***/mysql_mha
manager__log=/***/manager.log
remote_workdir=/**/mysql_mha
ssh_user=root
repl_user=repl
repl_password=123456
ping_interval=1
master_binlog_dir=/**/***
master_ip_failover_script=
secondary_check_script=/
server1
hostname=192.168.3.**
candidate_master=1(可以参加主服务器的候选)
server2
hostname=192.168.3.**
candidate_master=1(可以参加主服务器的候选)
server3
hostname=192.168.3.**
no_master=1(不可以参数主服务器候选)
启动MHA nohup master_manager --conf=/etc/mha/mysql_mha.cnf(指定配置文件)
优点:
使用perl脚本语言开发及完全开源
可以支持基于GTID的复制模式
MHA在进行故障转移时更不易产生数据丢失
同一个监控节点可以监控多个集群
缺点
需要编写脚本或利用第三方工具来实现vip的配置
MHA启动后只会对主数据库进行监控
需要基于SSH免认证配置,存在一定的安全隐患
没有提供从服务器的读负载均衡
读写分离和负载均衡
由程序实现读写分离
优点:
由开发人员控制什么样查询在从库中执行,由此比较灵活
由程序直接连接数据库,所以性能损耗比较少
缺点:
增加开发量,是程序代码复杂
人为控制,容易出现错误
由第三方中间件读写分离
mysql-proxy
MaxScale----MariaDB
优点
由中间件根据查询语法分析,自动完成读写分离
对程序透明,对已有程序不用做任何调整
缺点
由于增加中间层,所以对查询效率有损耗(高并发才会严重体现)
对于延迟敏感业务无法自动在主库执行
MaxScale
认证插件
协议插件
路由插件
readconnroute
readwritesplit
监控插件
日志和过滤插件
安装
下载软件包
yum 相关支持包
rpm 安装
B-tree索引
能够加快数据的查询数据
更适合进行范围查找
什么情况会使用:
全值匹配的查询
匹配最左前缀的查询
匹配列前缀查询
匹配范围值的查询
精确匹配左前列并范围匹配另外一列
只访问索引的查询
使用限制
如果不是按照索引最左列开始查找,则无法使用索引
使用索引时不能跳过索引中的列
not in 和 <> 操作无法使用索引
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引
Hash索引
Hash索引是基于Hash表实现的,只有查询条件精确匹配Hash索引中的所有列时,才能够使用到Hash索引
对于Hash索引中的所有列,存储引擎都会为每一行计算一个Hash码,Hash索引中存储的就是Hash码
限制:
hash索引必须进行二次查找(先找hash码再匹配值)
hash索引无法用于排序
hash索引不支持部分索引查找也不支持范围查找
hash码可能存在hash冲突(不能在数据值差别不大的列上建立索引)
使用索引带来的好处
索引大大减少了存储引擎需要扫描的数据量
索引可以帮助我们进行排序以避免使用临时表(B-TREE索引)
索引可以把随机I/O变成顺序I/O
索引带来的损耗
索引会增加写操作的成本
太多的索引会增加查询优化器的选择时间
建立索引的策略
索引列上不能使用表达式或函数
前缀索引和索引列的选择性(索引的选择性)
create index index_name on table(col_name(n)) (innodb有700多的大小限制)
索引的选择性是不重复的索引值和表的记录数的比值(主键索引和唯一索引是最好的索引选择)
联合索引(如果每一个都建立索引会使得索引过大)
选择索引列的顺序很重要(mysql优化器是where条件从左到右识别索引的)
经常会被使用到的列优先,放在最左边
但是如果是数据差别性很小的列(例如性别),也不适合放在最左边
宽度小的列优先(不违反选择性的原则下)
覆盖索引
优点
可以优化缓存,减少磁盘IO操作
可以减少随机IO,变随机IO操作变为顺序IO操作
可以避免对Innodb主键索引的二次查询
可以避免MyISAM表进行系统调用
缺点
某些存储引擎不支持覆盖索引
查询中使用了太多的列
使用了双%%号的like查询
使用索引来优化查询
索引索引扫描来优化排序
通过 order by
按照索引顺序扫描数据
索引的列顺序和Order By子句的顺序完全一致
索引中所有列的方向(升序、降序)和Order by子句完全一致
Order by中的字段全部在关联表中的第一张表中
使用B-TREE索引模拟Hash索引优化查询
只能处理键值的全值匹配查找
所使用的Hash函数决定着索引键的大小
利用索引优化锁
索引可以减少锁定的行数
索引可以加快处理速度,同时也加快了锁的释放
索引的维护和优化
删除重复和冗余的索引
primary key(主键索引),unique key(唯一索引),index(单列索引) 一个列上建立多个索引
index(a),index(a,b) 创建联合索引,造成冗余的索引
primary key(id), index(a,id) 创建联合索引,造成了冗余的索引
pt-duplicate-key-checker h=127.0.0.1 使用工具来检查重复索引和冗余索引
查找未被使用过的索引
利用sql来查找表的索引使用次数
更新索引统计信息及减少索引碎片
analyze table table_name(innodb引擎中查找的值可能是估算值)
optimize table table_name(减少索引碎片和表的碎片,但是使用不当可能会锁表)
获取有性能问题sql的三种方法
通过测试/用户反馈获得存在性能问题的sql
通过慢查询日志获取存在性能问题的sql
实时获取存在性能问题的sql
慢查询日志
slow_query_log = on 启动慢查询日志
可以通过脚本定时开启
slow_query_log_file = /**/slow_query_log 指定慢查询日志的存储路径及文件
long_query_time = 0.001秒(单位是秒,默认10秒) 指定记录慢查日志sql执行时间的阀值
long_queries_not_using_indexes = 是否记录未使用索引的sql
常用的慢查日志分析工具
mysqldumpslow 官方附带
pt-query-digest \
尽可能在从服务器执行慢查询日志(但是如果是写的话,还是要主服务器)
实时获取性能问题sql
SELECT id,'user','host',DB,command,'time',state,info FROM information_schema.PROCESSLIST WHERE TIME >= 60秒()
sql的解析预处理及生成执行计划
sql处理的阶段
客户端发送sql请求给服务器
服务器检查是否可以在查询混存中命中该sql
通过Hash大小敏感在内存中查找
用户权限
对缓存加锁,降低查询处理的效率(对读写较为频繁的系统,还是建议关闭查询缓存)
query_cache_type 设置查询缓存是否可用
query_cache_size 设置查询缓存的内存大小
query_cache_limit 设置查询缓存可用存储的最大值
query_cache_wlock_invalidate 设置数据表被锁后是否返回缓存中的数据(默认关闭)
query_cache_min_res_unit 设置查询缓存分配的内存块最小单位
服务器端进行sql解析,预处理,再由优化器生成对应的执行计划
解析sql
预处理
优化sql执行计划(可能影响mysql执行计划的因素)
统计信息不准确
执行计划中的成本估算不等同于实际的执行计划的成本
mysql服务器层并不知道哪些页面在内存中
哪些页面在磁盘上
哪些需要顺序读取
哪些页面需要随机读取
mysql优化器所认为的最优可能与你所认为的最优不一样
mysql是基于成本模型选择最优的执行计划---往往可能不是最快的执行计划
mysql从不考虑其他并发的查询,这可能会影响当前查询的速度
mysql有时候也会基于一些固定的规则来生成执行计划
例如符合全文索引的可能会执行全文索引
mysql不会考虑不受其控制的成本
存储过程、用户自定义的函数等
mysql查询优化器可优化的sql类型
重新定义的表的关联顺序
优化器会根据统计信息来决定表的关联顺序
将外连接转化为内连接
where条件和库表结构
使用等价变化规则
优化count()、min()和max()
利用B-TREE索引来查询最小值或者最大值
mysql如果执行此次处理可能会在执行计划出现:select tables optimized away (优化器已经从执行计划中移除了该表,并以一个常数取而代之)
例如对MyISAM表进行count()处理时,会使用MyISAM引擎的特性查询一个常数来返回统计值。
将一个表达式转化为常数表达式
使用等价变换规则
子查询优化(某些情况下)
转换为关联查询
提前终止查询
使用了limit查询
如果优化器能了解sql语句会返回null值,会提前终止查询,例如查询id=-1,但是id不会为负值的情况下,优化器会终止查询
对in()条件进行优化
跟踪执行计划,调用存储引擎API来查询数据
将结果返回给客户端
确定查询处理各个阶段所消耗的时间
使用profile
set profiling = 1;(session级配置)启动profiles命令
show profiles; 查看每一个查询所消耗的总时间的信息
show profiles for query N;查询每个阶段所消耗的时间
show profiles cpu for query 1(query_id); 查询cpu的信息
show profiles mysql 官方不推荐使用,推荐使用以下另一种方式去查询会更好,而且该命令可能会在以后的版本中被移除;
使用performance_schema(5.6以后开销就会好很多了)
启动需要执行两个update语句,打开监控、历史记录表的信息
UPDATE 'setup_instruments' SET enabled='YES',TIMED='YES' WHERE NAME LIKE 'stage%';
UPDATE setup_consumers SET enabled='YES' WHERE NAME LIKE 'events%';
只要执行了sql就是对全局有效的
查询语句
特定sql的查询优化
大表的数据修改最好要分批修改
修改大表的表结构
对表中的列的字段类型进行修改、改变字段的宽度时还是会锁表
利用老表建立一个新表,同步数据,同时对老表建立一个触发器,使老表新增数据时同步到新表中,当两个表数据相同时,对老表加排他锁,然后进行老表和新表的名称互换,达成修改表结构的目的;
可以利用工具 pt-online-schema-change
如何优化not in 和 <>查询
使用left join 优化 not in
使用汇总表优化查询
数据量大的时候count()会很慢
使用建立一个汇总统计表,每天固定时间执行存储过程收集数据
查询当前统计数据时,就可以将汇总统计表的记录+新增加的记录数,由于查询新增加的记录数时应该是会用了条件查询语句,那么就可能使用了相应的索引(覆盖索引),因此效率就会大大提升;
数据库分库分表的几种方式
把一个实例中的多个数据库拆分到不同的实例
把一个库中的表分离到不同的数据库中
数据库分片
对一个库中的相关表进行水平拆分到不同实例的数据库中
如何选择分区键
分区键要能尽量避免跨分片查询的发生
如何存储无需分片的表
每个分片中存储一份相同的数据
使用额外的节点统一存储
如何在节点上部署分片
每个分片使用单一数据库,并且数据库名也相同
将多个分片表存储在一个数据库中,并在表名上加入分片号后缀
在一个节点中部署多个数据库,每个数据库包含一个分片
如何分配分片中的数据
按分区键的hash值取模来分配分片数据
按分区键的范围来分配分片数据
利用分区键和分片的映射表来分配分片数据
如何生成全局唯一ID
使用auto_incremment_increment 和 auto_increment_offset 参数
使用全局节点来生成ID
在redis等缓存服务器中创建全局ID
oneProxyp分片工具(并不完美)
后续会有专门的一章去学习MyCat
对数据库服务可用性进行监控
如何确认数据库是否可以通过网络
mysqladmin -umonitor_user -p -h ping
telnet ip db_port
使用程序通过网络建立数据库连接(最好的方式,模拟应用程序连接数据库)
如何确认数据库是否读写
检查数据库的read_only参数是否为off
建立监控表并对表中数据进行更行
执行简单的查询 select @@version
如何监控数据库的连接数
show variables like 'max_connections';
show global status like 'Threads_connected';
Threads_connected / max_connections >0.8
对数据库性能进行监控
记录性能监控过程中所采集到的数据库的状态
如何计算QPS和TPS
如何监控数据库的并发请求数量
数据库系统的性能会随着并发处理请求数量的增加而下降
show global status like 'Threads_running'
并发处理的数量通常会远小于同一时间连接到数据库的线程的数量
如何监控innodb的阻塞
可以利用sql查询innodb引擎下线程阻塞时间,可能无法抽取阻塞的sql(sql已经执行完的情况下)
对主从复制进行监控
show slave status
监控主从复制延迟
使用多线程的程序同时对于主从服务器的状态来检查
如何验证主从复制的数据是否一致
pt-table-checksum
对服务器资源的监控
(未完待续;………………)