Mastering MariaDB 神秘的MariaDB 中文翻译版
是某群的哥们义务翻译的,宣传一下,还没时间时间读,粗滤看了全部翻译完了300多页佩服
https://github.com/CMant/Mastering-MariaDB- 原地址:如果你需要读,请star一下
Mastering MariaDB
中文译本
原著:Federico Razzoli
译者:虫神 写在前面的话:
这个项目,姑且称之为项目吧,从去年10月份就开始了。当时官网挂的还是mariadb10.1。今年的4月份其实已经翻译完了,但是还需要大量的整理工作。断断续续的,持续到现在。截至到发版之前,我又上MariaDB的官方网站看了看,稳定版已经更新到10.2 了。xtradb已经被废弃,换成了innodb。看了看原因,巴拉巴拉说了一堆。其实用我们高中物理老师的一句话就可以概括——“懒”。当然,除此之外,MariaDB基金会还在percona XtraBackup 的基础上开发了mariadbbackup,用以备份。实测了一下,安装更加方便,功能也更强大。原文描述为“based on and extended”。 也就是说,本书现在并不是最新的了,不仅是小版本小功能的区别,甚至在存储引擎级别也做了重大的调整。但是阅读这本资料并不会有什么太大的影响。一本书,如果能吸收它80%的知识,那么就已经算是很不错了。
关于这本书的英文原版,我需要说几句。这本书非常适合有oracle基础的dba来阅读,如果对ACID和oracle的系统架构了解的比较深入,那么阅读本书将会非常容易。我是在地铁上下班的期间读完的。觉得这本书很好,然后又想去做一点别人不敢想或者想了懒得去做又或者做了一半没能坚持下去的事:翻译它!自我11年开始进入大学学习时,就有了一个想要翻译一本技术类英文原著的想法。没想到时隔6年,才终于实现。翻译这本书到一半的时候,其实我是有点坚持不下去的。但是想了想自己吹下的牛与已经付出的辛苦。咬了咬牙还是坚持下来了。
翻译的质量其实并不是太好,各位读者还请多多包含。我毕业于河北科技师范学院,学历是专科,四级考了两次都没过。但好在本身比较喜欢英语,而且技术类单词也有限。咬咬牙还是翻译了下来。在翻译本书的过程中也发现了原著中的一些错误。我并非是MySQL方面的专家,对MySQL也并不是非常熟悉,所以对原文中的所有知识点去进行验证的也非常有限。但是如果原文与事实有误被我查出来的,我都在旁边做了标注。至于一些原文的逻辑错误。比如前言不搭后语的,也都标注了出来,其中还贴了一些原文上去供读者自行参详。
“我只是希望,在自己不断成长的道路上,可以为这个行业去做点什么。”我们在QQ群里吹水的时候,感谢这位大哥给予了这样的评价: 除去翻译这本书,早在读大学的时候,那时候为了完成数据库作业,老师要求有客户端,C,B不限,大部分同学都在用VB,PHP,JAVA或者去DOWNLOAD一份的时候,我脑子一抽,决定用C++和QT做一个客户端出来。当时关于qt的资料中文版非常少。我当时也仅仅是参考了国外的一套qt视频和国内的一些博客。完成作业之后,我又做了一套c++ QT教程,放在自己的博客上,算是国内仅有的几个(??)公开免费放出来的QT中文视频教程了吧。(喏,这就是地址,当年我还瘦着呢 blog.sina.com/cotxtan)。没想到居然还有淘宝卖家拿去卖。销量惨淡 。
关于行文,因为水平和时间都有限。行文不畅和排版不统一的地方请多多包涵。我是双鱼座,别喷,我玻璃心。
关于发布,我会把PDF和WORD版本都放在GITHUB上,如果哪些大神看到了其中某些错误,不管是我翻译的还是原文的错误。可以直接在原版上进行标注。我会感谢大家贡献的知识。
关于盈利
压根就没想过这回事
本书相关参与者
Josh King 供职于Kualo Web Hosting 担任高级系统软件工程师,在数据库管理方面有着十多年的工作经验。
同时他也是一名高性能系统和linux内核开发专家。作为几个著名软件组织成员之一,为linux和开源项目发展做出了巨大的贡献。比如LOPSA和免费软件基金会
他专注于基于windows和BSD平台的高可用和云相关的解决方案,被LOPSA授予 杰出专业信息技术奖。同时,他还编写了大量基于linux系统的调优文档。 他现在和他的老婆孩子定居德州。
Dainiel parnell 早年有大量计算机技术相关的经验,开始工作接触的机器就是AIM-65,后来有VIC-20,Commodore 64,Apple2 Commodore AMiga,ICL concurrent cp/m86 目前他是个果粉。用的都是mac。
Dainiel parnell喜欢用Ruby在Rails上处理他的医疗工业网站。前端玩javascript,后端搞Erlang.
Dainiel parnell在工作和思考之余喜欢和家人待在一起。不幸的是,他6岁的儿子在去年死于一种被称为板条疾病的神经系统错乱疾病,无法医治。
Giacomo Picchiarelli 是一名在数据驱动设计和MySQL管理方面有着6年工作经验的软件测试工程师。Giacomo Picchiarelli在linux系统和测试驱动开发上有着很强的技术背景。
Philipp Wollermann 现供职于谷歌德国公司,担任软件工程师,Philipp Wollermann搞Mariadb是因为他自己要用MariaDB,一晃就是5年。他还为东京的 CyberAgent搞过web应用优化,数据库性能调优。 关于作者
Federico Razzoli 是一名软件开发者,数据库专家和自由软件支持者,他从2000年开始从事网站和数据库应用方面的工作,并且在MySQL方面有较长期的工作经验,现在是maraiadb社区的一名活跃成员
关于译者
虫神,充满浪漫与才情的DBA,一点也不出名
14年毕业于河北科技师范学院计算机网络技术专业。
现就职于天津一家金融公司,担任DBA。
关于本书
简单介绍一下这本书,本书并不是给菜鸟来做入门的,但是如果你和我一样属于当了很长一段时间的的oracle database管理员,对关系型数据库比较了解,对于acid的过程比较清晰,有良好的系统架构基础知识,那么这本书可以作为你用来学习MySQL类数据库的快捷工具。完全水平过度,不会让你有浪费时间的感觉。对于oracle dba,完全感觉不到这本书哪里难以理解。
如果您没有学习过任何关系型数据库。我建议您在读过learing MySQL and MariaDB或者其他相关基础书籍之后再来读这本书。因为这本书的作者也是这么建议的。
目录
(我连页码都没做。)
第一章:Mariadb学习必知
Mariadb架构
命令行客户端
存储引擎
Xtradb和innodb
tokudb
myisam和aria
其他
日志
MariaDB 缓存
Innodb数据结构
认证和安全
information_schema 数据库介绍
performance_schema 数据库介绍
与MySQL想通之处以及其他一些dbms
MariaDB 资源
总结 第二章 排错
MariaDB报错概览
SQLSTAT 值
ERROR 编号
ERROR 消息
自定义错误
show warnings 和 show errors 命令
诊断区
GET DIAGNOSTICS 命令
错误日志
错误日志格式
通过错误日志排错示例
系统日志
一般查询日志
一般查询日志格式
general_log 表
通过一般查询日志排错示例
日志机制
刷新日志
基于文件的日志循环
基于表的日志循环
SQL_ERROR_LOG 插件
存储过程排错要点
使用SQL_ERROR_LOG插件对存储过程排错 第三章:优化查询
慢查询日志
慢查询日志文件格式
slow_log表
来自Percona Toolkit的pt-query-digest命令
索引介绍
表统计信息
存储引擎和索引
用EXPLAN命令研究执行计划
理解explan输出
select 样例
内部临时表,文件
union查询
索引访问方法
join子句中的索引优化
子查询优化
总结 第四章:事物和锁
innodb 锁
锁模型
锁类型
锁诊断
sql语句中的锁
一致性读
非重复读
幻读
一致性读
读锁
死锁
事物
事物的声名周期
事物的隔离级别
read uncommitted 隔离级别
read committed 隔离级别
repeatable read 隔离级别
serializable 隔离级别
事物访问模型
元数据锁
总结 第五章:用户和连接
用户账户
通过角色授权
通过secure socket layer(ssl)连接到MariaDB
认证插件
线程池触发
监控线城池
配置线程池
UNIX上配置
windows上配置
优化配置变量
解决线程池中阻塞的线程
监控连接
进程状态
断开连接
总结 第六章 缓存
innodb cache
innodb page
innodb buffer pool
new page和old page
buffer pool 实例
脏页
优先读优化
诊断buffer pool性能
导出和导入buffer pool
innodb buffer切换
双写buffer
myisam key cache
LRU和中点插入策略
key cache 实例
key cache 段
预加载索引
aria page cache
查询cache
配置查询cache
查询cache相关信息和状态
子查询cache
替代查询缓冲方案
为表创建cache
基于独立会话的cache
总结 第七章:innodb 表压缩技术
innodb压缩技术引言
innodb压缩条件
“file-per-table”模式
innodb文件格式概要
创建innodb压缩表
innodb压缩表实操
监控innodb压缩性能
innodb_cmpmem 表
innodb_cmp_per_index 表
innodb_cmp 表
其他压缩方案
总结: 第八章:备份和灾难恢复
备份种类
逻辑备份和物理备份
热备份和冷备份
全量备份和增量备份
备份和复制
备份操作步骤
通过mysqldump创建dump文件
纯文本备份
mysqldump命令中的 -tab选项
mysqlimport导入dump文件
通过select...into outfile 创建纯文本文件
通过show create table 命令导出表结构
通过load data infile导入dump 文件
利用选项分离操作
创建和还原dump文件示例
在connect和csv存储引擎上操作备份
物理备份
什么文件应该被复制
表文件
日志文件
配置文件
热物理备份
文件系统快照
通过rsync命令实施增量物理备份
非停机的文件复制
为增量备份使用 binary log
percona xtrabackup
备份实操
全量备份
部分备份
备份
完整备份
部分备份
还原备份
还原完整备份
还原部分备份
备份安全
修复表
回复innodb表
校验表
事物日志
强制数据回复
修复 非innodb表
check table 命令
repair table 命令
修复csv表
通过myisamchk和aria_chk工具修复表
myisam和aria自动恢复
总结 第九章:主从复制
复制引言
复制如何工作
复制线程
并行复制
“从”端日志
选择 binary log 格式
基于语句的 bingary log
基于行的binary log
混合binary log
存储过程中的binary log
配置主从复制
配置一个新的“主”
配置一个新的“从”
启动“从”
检查“从”是否运行
重新配置已有的“从”
导入数据到“主”
从”主“导入数据到”从“
从”主“中导出数据
从”从“中导出数据
过滤binary log 条目
SET SQL_LOG 命令
@@skip_replication 变量
在”从“中过滤复制条目
校验binary log 条目
配置并行复制
让”从“懒一点
多”主“一”从“复制
复制日志
循环binary log
循环 relay log
"从"日志状态
检查复制错误
checksum table 命令
pt-table-checksum 工具
文件校验
查询校验
排错
”从“不启动
”从“延迟
总结
第十章:表分区
分区支持
分区类型和分区表达式
分区表达式
索引和主键
分区名称
分区类型
range类型
list 类型
columns 关键字
hash和key类型
linear关键字
分割子分区
管理分区表
查看分区信息
更改分区定义
修改range和list分区
修改hash和key分区
分区和表之间复制数据
操作语句机制
分区的物理文件
优化查询
分区修剪
分区检索
总结 第十一章:数据分片
多个磁盘间分散文件
选择表文件的路径
innodb日志文件
配置undo
配置redo
FEDERATEDX和connect 存储引擎
创建FEDERATEDX表
定义远端服务器连接dblink
创建 mysql connect 表
发送sql语句到远端服务器
合并多了个connect mysql 表
SPIDER存储引擎
SPIDER工作原理
安装SPIDER存储引擎
创建SPIDER表
查询和错误日志
远端服务器上执行特殊语句
spider_direct_sql() 函数
spider_bg_direct_sql()函数
总结
第十二章:mariadb galera cluster
mariadb galera cluster 关键概念
galera cluster 引言
同步复制
安装集群
需求
安装
启动节点
选择节点url
节点规定
快照状态传输
增量状态传输
脑裂
galera 仲裁器
配置集群
galera 中重要的系统变量
一般集群设置
性能和可用性
设置快照状态传输模式行为
处理galera限制
设置wsrep参数
监控和排错
告警脚本
检查状态变量
集群健康状态
独立节点状态
复制健康状态
网络性能
负载均衡
galera 集群的限制
galera负载均衡
总结 索引 前言
数据库与我们的生活息息相关,我们一天会使用好几次然而你可能并没意识到。比如,打电话算一个,订酒店,访问网站或者是其他。 这些数据库非常庞大复杂,但是它们有很多都是由mysql或者是mysql的同门兄弟mariadb组成的。
mysql是著名的LAMP四大组件之一,linux,apache,php和mysql。网站技术上用的最多的就是lamp。这就是为何很多it从业者都听说过mysql。实际上mysql在上世纪80年代就有了。但是真正风靡整个it行业是在2000年到2001年。这段时间是动态网站技术爆发的一年。其免费,开源易学等特性使得mysql在网站领域大行其道。
由于mysql天生励志,它的很多功能特性增长非常迅速。曾经还被postgresql支持者嘲笑不支持很多DBMS的关键特性,比如事物和外键。但是没多久,mysql就实现了这些功能,因为这些功能对于很多用户都极为需要(虽然普通用户并无感知)。例如。它比postgresql支持主从复制早了10年。比postgresql更快更可靠。随着时代的发展,mysql变成了一个功能完善,特性丰富的关系型数据库
为什么Mysql之父Monty Widenius离开了这个项目,而去搞一个衍生产品MariaDb?原来在2005年,oracle买下了innodb存储引擎,2008年,Sun公司买下了mysql,转年又被oracle买下。从那以后,mysql仿佛陷入了一场巨大的阴谋当中:一个背景庞大的,持有很多专利和昂贵软件的的商业公司霸占了本来免费的mysql。真可谓,司马昭之心路人皆知。
但是话说回来,我们也必须承认oracle确实也花费了大量精力和资源投在了mysql身上。并且在一些方面做出了很大的贡献,比如innodb。然而,天有不测风云,谁能保证oracle会一直这么做下去?要是有一天oracle把mysql闭源了呢?或许只有oracle负责人才能回答这些问题。
我们可以确定的是,mysql现在仿佛并不像从前那么开放了。oracle员工开始对公布出的bug有所保留。没有相关信息或测试在乎发布的版本是否有安全漏洞。更新也有点疲软。并且一些社区网站,那些包含大量mysql信息的wiki站也都不见踪影了。
就在此时,一只异军在社区网站突然崛起,它就是mariadb。很多重要的新特性,比如多源复制和角色功能都是最先被社区成员开发出来的。mariadb的bug跟踪纪录和项目管理模式允许我们去了解在整个过程中有什么bug,什么新特性。“新版本是什么样的?”,"它什么时候发布?"诸如此种问题,一些活跃的开发者还会与用户进行邮件,或者是IRC沟通。当mysql的文档开始变得专有隐蔽的时候,mariadb的文档将会一直免费下去,并且通过公共wiki提供社区支持。
最重要的,mariadb有基金会,跟apache基金会一样.Monty Widenius是这些收益的负责人。有钱,咱就能保证mariadb的源码一直免费安全的发展下去。不仅如此,基金会的存在,还使得mariadb衍生出一些周边产品,比如《mariadb knowledge base》mariadb基金会网站如下:https://mariadb.org
从2009年的那一天,mariadb和mysql就走上了不同的道路。当mysql的开放程度越来越小气的时候,mariadb依然豪放地拥抱着全世界。这种开放精神才是科技进步的灵魂。来自全世界的mariadb的开发者和用户全身心地投入到mariadb成长过程中,共同造就了mariadb的今天。
这本书包含了管理mariadb和集群服务相关的技术。对你在mariadb数据库开发和管理上大有裨益。mariadb将会一直兼容mysql最新的特性。这本书的开始会讲一些读者应该了解的基本特性和机制。其中包括很多真实环境中的问题,比如mariadb 报错,日志,锁。你将会学习到如何提高服务器的查询性能。同时还包含了如何选择恰当的备份策略和恢复技术,使用复制功能完成数据切片,mariadb galera集群,spider存储引擎相关的,比如诊断,排错方法也会详细说明。不管是来自mysql的特性,还是mariadb独有的特性,都会在本书涉及。插件和相关工具的开发社区也会提到
mariadb已经枕戈待旦,随时等待高性能和高可用的危机。来吧,让我们一起进入mariadb的世界。 内容简介:
第一章:mariadb学习必知。探讨了一些关键特性和组成。比如存储引擎和日志。一些最重要的mariadb相关专业资源也会在这里涉及
第二章:排错,阐述了mariadb中如何处理bug,mariadb如何生成错误,我们可以从哪里找到这些报错。
第三章:优化查询。主要讲了查询的优化,首先我们讨论了需要找出哪些操作导致了慢查询的出现。而后又讨论了mariadb优化器去执行一个查询时使用的相关算法,比如索引合并和子查询优化算法。
第四章:事物和锁。阐述了mariadb在并发情况下如何使用锁来保证每个事物之间的隔离,以及锁是如何影响性能的
第五章:用户和连接 讨论了如何管理mariadb中活动的连接和用户账户。包括权限问题,用户资源获取,认证方法,ssl连接,以及线程池、
第六章:cache 阐述了存储引擎的cache技术。innodb buffer pool,myisam key cache,以及aria page cache。随后又提到了查询和子查询方法,以及其他缓存结果集的方式。
第七章:innodb 表压缩 讨论了innodb压缩表技术。为我们展示了如何创建一张压缩表然后监控它的性能。最后还比较了不同的压缩方案
第八章:备份和灾难恢复 阐述了mariadb和第三方工具提供的备份方法。以及如何选择一个备份计划,不同的备份种类以及在需要的时候如何还原它们
第九章 复制 讲述了如何搭建一套复制环境。以及mariadb 10.0中包含的最新特性,即并行复制和多源复制
第十章 表分区 为我们展示了如何把大表分割成几个分区,分区文件存放在不同的存储设备上。不同的分区类型,以及根据不同情况如何选择最优分区类型等等都会涉及到
第十一章: 数据分片 探讨了把数据通过多个磁盘和服务器分布存储的主要方法。这些存储引擎允许读写远端服务器数据,诸如spider, federatedx ,connect.
第十二章: mariadb galera cluster .涉及到mariadb的集群技术。阐述了如何配置集群,添加节点,监控性能,以及解决一般问题。 为了学习这本书,你需要有:
运行任何操作系统的PC机,linux是最优选择,因为mariadb最有可能运行在linux上,并且本书也基本专注于linux系统。然而,这些命令可以不被修改地运载在任意UNIX系统中。有必要的话,我们也会提到windows
mariadb 和mariadb galera cluster可以在mariadb官方网站上下载到。需要的第三方工具会在相关章节提及。其中涉及到的系统命令,不出意外每个linux版本应该都有
谁会学这本书?
这本书应该算是mariadb或者是mysql的中级读物,如果你对mariadb有一个比较清晰的认知,可以管理mariadb服务,或者配置安装。当然没问题。如果你是其他关系数据库的专家,当然也没问题。但如果是刚入门,还是建议先去读一读《mariadb knowledge base》这类的初级读物。
特别说明的是,这本书的读者应该熟知以下几个知识点
1 关系数据库的基本概念
2 sql语句,至少基本水平
3 配置文件的语法和结构
以下几个知识点,如果读这本书的话倒不是很必要,但是还是推荐你最好学习一下
1 如何编写windows或者linux 的自动任务
2 如何编写能与mariadb交互的脚本
3 mariadb程序,比如存储过程,事件,触发器 读者反馈(我为什么要翻译这个??):
非常荣幸能获得读者的反馈,以便让我们知道您是如何看待这本书。哪里喜欢,哪里觉得写得不好。读者的反馈对于我们是最大的馈赠。
反馈可以通过写一封简单的邮件到feedback@packpub.com,受累以这本书的标题作为邮件标题发送即可。
如果本书中有些内容是您所专长的,您可以发表您的文章或者是您的书籍。作者专栏在www.packtpub.com/authors 客服:
xxxxx(不议)
下载示例代码:
您可以从www.packtpub.com 下载所有的示例代码文件,前提是您已经购买过的书。如果您是从别的地方买的,您可以访问www.packtpub.com/support 注册一下,然后我们会直接发邮件给您 errata
xxxx
piracy
xxxx
questions
您可以通过questions@packpub.com联系我们,如果您对此书有任何疑问,我们将会竭诚为您服务。 第一章 mariadb学习必知
这章主要描述了mariadb的一些架构知识,再次声名,并不意味着第一章就是能给初学者看的。其中有些知识吃透了才能理解这本书
本章我们将讨论以下几方面
1 mariadb 架构
2 sql语句工作流程
3 命令行
4 存储引擎和其他特性
5 日志
6 cache
7 用户认证和权限
8 information_schema和 performance_schema数据库
9 与mysql同源分支和其他dbms
10 资源网站 mariadb架构
Monty Widenius 在2009年开发了mysql的衍生版本----mariadb,此数据库由社区开发维护Monty Widenius是mysql之父。可惜后来mysql被oracle收购了。第一版的mariadb基于mysql 5.1开发出来。以后的版本一般都和mysql的版本号是一样的,有些特性是和percona server 一样的。另一些特性则非常像当今的一些主流产品。
取自percona的一个非常重要的功能就是xtradb。这是innodb存储引擎的一种衍生版本。innodb是现在mysql和mariadb中的默认存储引擎。xtradb修补了一些oracle发布innodb时候就暴露出来的,但是oracle懒得去解决的漏洞。同时它还有一些小的性能提升和新特性。诸如协议,api,以及许多sql语句,凡是能在mysql中跑的,在mariadb中统统没问题。即便是为mysql写的插件同样OK。正因如此,一些基于mysql的应用程序可以不加修改地迁移到mariadb上。但是话说回来,如果你想用之前mysql没有的,但是mariadb有的特性,可能不行。如果开发人员不关心这些特性。那么应用程序可以跑在这两种数据库上。读者一般都是有一定的关系数据库基础知识,现在咱们快速浏览一下mariadb的架构。在本章的开始,已经列出了所有组件。剩下的就是在本章详细地探讨它们。
架构图如下: 如你所见,mariadb接收sql语句,解析,然后返回结果集。让我们看看详细过程是如何进行的
1 当客户端连接到mariadb的时候,会认证客户端的主机名,用户,密码,认证功能可以做成插件。
2 如果登录成功,客户端发送sql命令到服务端
3 解析sql语句
4 服务端检查客户端是否有权限去获取它想要的资源
5 如果查询已经存储在query cache当中,那么结果立即返回
6 优化器将会找出最快的执行策略,或者是执行计划。也就是说优化器可以决定什么表将会被读,以及哪些索引会被访问,哪些临时表会被使用。一个好的策略能够减少大量的磁盘访问和排序操作等,这部分内容我们将会在第三章的 优化查询中讨论。
7存储引擎读写数据和索引文件,cache用来加速这些操作。其他的诸如事物和外键特性,都是在存储引擎层处理的。
mariadb和存储引擎使用日志来记录接收到的语句,发生的错误,数据变更等等。大多数日志都是可选的,然而,有些日志对于管理工作是必须的。比如binary log 用来备份和复制。日志会在后面的章节讲解。
mariadb有一些选项可以影响服务器的行为。其中很多是动态的,意味着你可以在服务器运行的时候改变它们。也有一些是静态的,意味着服务器一旦启动就不能再变。有一些变量是会话层的,意味着可以在任何当前独立连接的的会话中改变它们,有一些是全局层的,改变将会影响到所有用户。有几种方法去决定在影响范围。比如通过命令行参数,命令文件。如果是动态变量,通过sql语句即可。mariadb通过命令读取配置文件。具体的路径要取决于操作系统。一般来讲,一台服务器运行一个mariadb实例。因为只用到一个配置文件。linux上是 /etc/my.cnf ,windows是在mariadb的安装目录。比如这种 c:\mariadb 10.0\my.ini
然而,这种模块化的配置方式在同一台机器上安装多个mariadb实例时是非常有用的。一些设置可以对所有实例生效。但是每个实例还能有自己独特的设置或者是覆盖常规设置。配置文件也可以放在用户的home目录中。但是此时只有在使用 --user 启动mariadb的时候才会被读到。通过命令行指明的参数将会覆盖到配置文件中的参数。这种方式在测试不同版本,不同变量的性能时非常有用
本书不会描述所有的选项,读者应该对最重要的几个参数很熟悉了。如果您想看所有参数的话,《Mariadb knowledge base》里面都有。
Mariadb实例其实就是个mysqld文件。在linux上,可以直接运行,但是一般都是由mysqld_safe脚本来调用。mysqld_safe脚本既可以启动实例,又可以重启意外终端的实例。在生产环境中比mysqld更安全。mysql.server脚本可以用在system V架构之下。这个脚本一般都会根据linux发行版重新命名,当一台服务器上安装了几个实例的时候,很可能会被命名成mysql_multi
命令行客户端
本书使用的示例都是在命令行中操作的,学习命令能够极大的提高效率。
mysql客户端检测命令结束是通过 ; , \g , \G 来识别的。下面的例子演示了这一点 //* 下载示例代码
开篇说了,要么你注册账号在他的网站上买书,买哪本才能下哪本的。如果你在别的地方买的这本书,就得去他那里注册账号,然后他发邮件给你,邮件地址 http://www.packtpub.com/support
*//
mysql客户端一般会有个提示符,当你刚连接到数据库的时候是这样的 在提示中,[none]代表了没有选中任何数据库,意思就是每次你执行sql语句的时候,数据库的名字必须被特别指定。使用use命令可以选择一个默认数据库。它的名字将会出现在提示中。比如这样: 当一行语句太长不得已分成两行的时候,提示符会变成这个样子: 如果我们忘了输入结束语句的符号,那么提示符就会变成这个样子来让我们注意到问题所在: 比如这里,mysql客户端不知道语句结束了,因为结束符号忘了输入了。
如果在一行的结尾有个单引号,那么单引号会出现在新的一行当中。新一行可以通过敲回车来输入新的字符,不过大多数情况会出错。比如下面这种情况。提示符帮助我们注意到了问题所在: 此处的问题所在是 “hello world” 后面的单引号没输入,故单引号会在下一行的提示中出现。
有时候命令行输出结果难以阅读,尤其是输出结果比命令行一行还长的时候。这种情况下,在命令的结尾输入\G 可以转换显示方式,比如这样。(就是把行转成一页。参考plsql developer上面的功能) 在linux系统中,可以使用 pager 程序来读取过长的输出结果。 pager通过关键字或者鼠标滚轮实现了翻页输出功能,诸如此类的命令有 less , more,lv (很多发行版中默认没有安装) 。比如我们使用less命令。可以这么输入: 之后的查询就可以通过 less来查看。如果希望关闭pager功能。运行下面的命令即可 有时候输出超级长,但是我们也就关心其中的几行,或者就一行。那么针对这种情况。我们可以使用pager中的 grep命令作为选项。下面的例子向我们展示了 “show engine innodb status” 这个命令。其中我们只关心线程状态,其中只要包含 “I/O thread” 这个字符串即可 另一个比较有意思的选项是pager功能中的 md5sum 命令。如果采用了这个命令。当一个查询执行的时候。其对应的 md5 hash值也会显示出来。这在比较两条查询结果集的时候非常有用。比如下面,我们检查两张表是否完全一样 在上面的例子中。查询结果会通过md5sum进行计算。他们的md5值会出现再命令行中。如你所见,值是相等的。因此我们能充分确定 t1 t2 是相同的
\tee 命令可以用来把当前客户端会话记录到文本文件中。在 windows 系统中。可以使用文本编辑器打开编辑。此时\p 不再有效。停止记录的话,使用 \notee 命令即可
默认情况下,sql语句warning 级别的警告不会出现在命令行中,但是会显示一个警告数。如果有警告,说明sql没有按照我们希望的形式运行。如果想看到所有的警告 ,使用大写的 /W 即可。如果要取消这种警告方式。再输入 小写的 \w 即可 上面的例子中。我们首先打开了警告提示,随后的select查询就生成了一个详细的警告内容。然后我们关掉了警告提示。再执行同样的语句,就只有一个警告数量了。
所谓工欲善其事必先利其器,当我们需要在命令行输入复杂的语句的时候。可以输入 edit命令或者 使用EDITOR环境变量来设置一个默认的编辑器,比如linux上默认的vi或者emacs(不要再因为这种事打架)。
mariadb 10.0 中,停止服务器无需使用 强制退出或者新开个控制台窗口调用mysqladmin命令。通过sql命令即可,即shutdown。 和其他很多管理命令需要super权限不同。这个命令有个单独的shutdown权限可以配置。通常是只有root有这些权限。退出客户端使用 小写的 \q 即可。示例如下: mysql客户端可以使用一种称为batch的文件。这个文件可以编写大量的sql语句。可以用来做比如恢复逻辑备份,或者根据应用需要创建一个数据库。执行的结果也可以单独写到一个文件中。语法是UNIX类的风格。但是windows下也可以用。比如这样: 如果想不登录客户端,直接在shell中执行一个命令然后看到结果。你可以这么做(真实的情况是你还要输入 -u -p参数) 存储引擎
正如之前章节所说,存储引擎在物理层管控数据。它负责数据文件,数据,和索引cache。这使得管理和读取数据变得更高效
每一张表,都有一个.frm 文件。这些文件包含着表的定义。通过服务器来创建和使用它们。
使用 “show engines” 命令或者通过查询 information_schema.engines表可以查看存储引擎相关变量。下面的示例展示了标准的 mariadb 10.0.6中存储引擎安装情况 以上输出中,有一列名为 support,代表了在当前服务器中此存储引擎是否可用
当表被创建的时候,可以指定一个存储引擎。如果没有指定,那么就使用默认参数来创建。默认参数通过系统变量 storage_engine 来配置,如图: 在 information_schema 数据库中,有一张 tables 表,其中有一列叫做 engine,其代表的就是此张表用的什么存储引擎,示例如下: Xtradb和innodb
innodb在 mariadb 5.5 和mysql 5.5中为默认引擎。 percona搞了一套innodb的衍生产物—— xtradb。percona修补了innodb中的一些bug,不仅如此,它还有一些innodb没有的特性(主要是在性能和监控方面)。默认情况下,mariadb使用xtradb引擎。当然和mysql中的 innodb是兼容的。在命令行中依旧使用“innodb”这个名字来代表xtradb。无论是查看引擎状态还是建表。
当然这种行为可以在编译阶段修改。任何新的代码都会有新的bug或者是意料之外的性能问题。xtradb也不例外。
//* 本书xtradb统称为innodb。除非我们在说一些innodb中没有,但是xtradb有的特性的时候 *// innodb 是一种高性能存储引擎。它支持很多功能,比如savepoint,XA,外键。savepoint可以在事物执行中任意位置间插入一个状态标记,以备需要时恢复到事物执行的某一点中。XA是一种特殊的事物,主要用来调用多个资源节点,也就是我们常说的分布式。在大多数情况下 innodb比其他引擎性能更好。由于此种原因,本书专注于xtradb,除非特别说明。为方便起见,这里把xtradb统称为innodb。除非我们在讨论一些innodb不支持的特性的时候。
innodb事物通过一种复杂的锁机制和undo日志来实现。每个锁可以针对一行或者多行进行操作。行通过索引进行定位。如果必要,undo日志可以用来回滚事物,undo日志可以存储在系统表空间或者是其他地方。
TokuDB
这套引擎由tokutek开发,在mariadb 5.5.中被收录。强制安装但是可以选择是否开启。它支持savepoint事物,XA,但是不支持外键和全文索引。和innodb有很大不同。它主要的特性是针对索引使用了一种新的数据结构——分形树。这种结构和通常的B树很相似,但是它每个节点都有个buffer,这些buffer里面包含了那些更深层的要变更的节点的数据。仅当buffer满了才会把这些变更一起应用。如果这些变更需要写磁盘,那么这就成了一个很重要的优化点,因为更少的写入和更大的块在性能上通常更快。正因如此,分形树数据结构不会存在碎片问题。
TOkuDB的另一个重要的特性是数据压缩,当然,具体的压缩级别取决于数据集。但是一般比其他的存储引擎有着更好的压缩比。使用这种策略的原因是之前提到的写操作都是一组一组一起进行的。TOkuDB的数据压缩特性不可关闭。
分形树和压缩功能使得TOkuDB更适合那些数据集太大以至于不能完全塞进内存的情况。就负载情况来讲,速度更优于innodb。除此之外,TOkuDB在性能和功能上就有点疲软了。
myisam和aria
在mysql和mariadb5.5之前,myisam曾经是它们的默认存储引擎。这种引擎对少量写,大量读进行了优化。从经验上来说,myisam非常适合用于经常往表中插入数据,但是很少有修改和删除操作的数据仓库或是报表系统。
myisam写每张表需要涉及两个文件:数据文件和索引文件,索引文件被毁是没事的,因为它可以被重建。复制数据文件(包括.frm文件)来对myisam表进行异机恢复是可行的。
myisam有三种数据格式:固定,动态以及压缩。固定格式指的是表中的每列长度是固定。换言之动态格式就是可以动态调整(类似char和varchar)以节约空间。固定格式速度更快更可靠并且更不容易产生碎片。压缩格式可以用来创建比较小的只读表。
aria是被用来设计作为myisam的继任者的。它支持日志和数据灾难恢复。aria中的数据改变是原子级的。要么全部应用,要么全部失败。aria使用一种不会产生碎片且速度更快的被称为PAGE的数据格式。但是它也可以使用固定或者动态格式用来兼容myisam,但是这样表就不再支持灾难恢复了。(crash-safe )
aria在并发性的表现上要优于myisam,并且《mariadb knowledge base》同样建议在新环境中部署aria存储引擎。诚然,用户还是会感觉到aria在并发写入上的不足,尤其是有多个索引的时候。
myisam和aria都不支持事物和外键,但是正如之前所说。每一个在aria表上执行的语句都可以被看作为一个事物。而且这两种存储引擎都是支持全文索引的。 MRG_MYISAM存储引擎也被叫做MERGE,能够建立在多张相同的myisam表上。通过这种方式,可以绕开操作系统的单个文件最大大小。
其他引擎
上面描述的存储引擎在一般场景都是可以应付得来的。当然也有一些特殊的环境需要一些其他的存储引擎才能解决。其中有一些引擎使用了非标准的输入输出方法,或者是非标准的查询方法,下面我们就展示一些极为特殊场景下的存储引擎。
1 OQRAPH 存储引擎由OPENQUERY开发。它拥有数和图的数据结构。数结构在数据库中有几种方法操纵,但是无论使用哪种方法,都有其缺陷,因为其相关的理论并不非常适合树形结构。OQGRAPH解决这些问题是通过把sql请求转译成特殊的树请求(这方面接触的少,不大清楚具体内容)。OQGRAPH在maria db5.2引入,在5.5版本暂时停止,在随后的mariadb10版本中又重新引入。
2 BLACKHOLE存储引擎继承自mysql 。BLACKHOLE表永远都是空的。修改永远不会生效,查询永远都是空结果集。
3 SPIDER存储引擎由kentoku shiba开发。它通过其他mariadb实例读写数据。支持XA事物。SPIDER主要设计用来做数据分片,关于数据分片内容我们会在第十一章 数据分片 中讨论。
4 CONNECT 存储引擎是mariadb中一种特殊的存储引擎。用来从外部资源中读写数据。其对应的数据资源可以是mariadb或者myslql,ODBC连接,文件,甚至是目录。文件可以是但不限于CSV,HTML表,二进制数据,其提供了API来支持不同的文件格式。数据能够被压缩成gzip。一个connect表可以用来从其他表传输数据。例如,可以把数据重组或者表合并成一张汇总表。CONNECT很有可能会废弃一些比较老的存储引擎,比如CSV这种访问CSV文件的引擎;FEDERATED同样是一种继承自mysql的存储引擎,它也可以向CONNECT那样连接到其他mysql或者mariadb实例的表。后来mariadb引入了federatedx,因为federated已经没人维护了。
5 ARCHIVCE存储引擎主要是用来压缩表。但是它有几个限制,比如插入数据后不能修改或删除,而且还相当慢。在当下环境,innodb,myisam或者是tokudb,表现都比archive好很多。
6 cassandraSE存储引擎是用来连接apache cassandra nosql的,它可以在mariadb的数据类型和cassandra的逻辑之间转换。他是mariadb特有的存储引擎,因为它需要使用mariadb的动态列来模拟cassandra的列族。
7 SphinxSE 存储引擎主要是允许mariadb访问存储在sphinx 数据库服务器中的表。sphinx的优势在于它出色的全文检索功能。
8 mroonga存储引擎是专门为全文检索设计的。它包含了日语,中文,韩国的字符集和语言。它通过快速几何索引为地理定位做出了优化。
9 sequence存储引擎不能够被创建成一张物理表。如果启用它,语句需要包含一些有着明确分隔符命名的虚拟表。??由于这种命名,sequence存储引擎会返回一系列数字。例如 seq_1_to_10 虚拟表返回1到10的结果集。seq_1_to_10_step_2同样会返回1到10 的结果集,唯一不同的是它的步进是2.
11 performance_schema 存储引擎仅仅被performance_schema数据库作为内部表使用。这就是为什么DBA会意识到有一些特定的语句存在,比如"show engine performance_schema satus" 这种命令可以用来查看 performance_schema数据库的内存占用量。
日志
mariadb服务器包含以下几种日志类型:
error log:这种日志包含了服务器运行时产生的错误。其包含了服务器错误(比如再启动过程中某个插件意外停止)和sql错误
SQL_ERROR_LOG: 这是一个在mariadb 5.5引入的插件,以sql语句写入文件的方式来记录生成的错误。相比error log,它只记录 sql错误。使用这种方法来检查存储过程或者触发器中的错误是非常容易的。
general query log: sql执行语句会被写入到文件中
slow query log: 这种日志主要用来记录那些执行时间很长或者是没使用索引的查询。这种日志用来找出应用程序或者数据库缓慢的原因是非常有用的。
binary log(binlog):依赖于特定的格式,这种日志会把数据变更记录成二进制格式。这种日志再实施增量备份,复制或者是数据库集群技术的时候会用到。
relay log:这种日志仅存在于 “主从复制”的从端,其包含了从主端接收到的数据。从端的每一条relay log都对应着主端的一条binary log。
innodb 包含了两种称为 undo log和redo log的日志。undo日志用来记录活动事物的变更向量(oracle中是这么解释,其实就是可以理解为变更前的映像)并且在有必要的时候回滚它们。redo日志用来记录请求的数据改变,并且可以用来进行实例恢复。
aria 有一种称为 aria log 的日志,其包含了那些没有应用到数据文件的数据。它可以用在数据库启动阶段对那些因为没有正确关闭数据库而导致的表错误进行恢复。对应的myisam 表日志就叫做 myisam log。
每一种日志都有其自己的一类文件。一般存储在数据库安装目录中的data目录。如果你特别指定,也可以放到其地方。然而,一些日志可以写到mysql数据库的系统表中,这种写入方式比较慢,但是它允许通过sql语句来查询相关信息。如果此时你使用的时csv存储引擎,那么你可以把这些日志以一种流行的格式导入到外部程序中。
由于日志写入非常频繁,因此使用buffer来提高写入性能。(通过批量写入以减少开销)当然了,写入日志会使得系统更加可靠。有一些变量可以调整buffer大小,dba可以根据需要的可靠性和速度做出相应调整。
日志会被周期性循环使用。意味着新的记录会写入到新的文件中,最老的文件可能会被删除。这种循环可以是自动进行(比如 binary log),也可以是用过FLUSH LOGS命令或者是mysqladmin工具
对于每一种用户需要读取的日志,都有相应的工具可以展示其内容。日志循环可以通过red hat linux中的 mysql-log-rotate脚本完成
mariadb cache
(因为cache和buffer 都可以被翻译成缓存,但是实际上我们知道意义不大一样,因此不做翻译)
mariadb 有几种不同的 cache可以通过不同的系统变量和启动参数根据特定的工作负载进行调整。通常来说,只有很少的几种日志需要去配置。通过改变几个小的选项,整个服务器性能可能会有巨大的改变。有一些cache能够解决很多特殊的问题。
innodb 中的 buffer pool 应该说是最重要的一种cache,其包含了innodb表的数据和键。在一套专用服务器系统中,通常buffer pool的大小为物理ram的70%。当然,这个值不是绝对的。最优值取决于很多因素。buffer pool有两个子链:new list和old list,其链大小也是可以配置的。其规则为:最小老化值的页必循存在于new list中。其设置决定了new list中可以保存最近多久读取到的数据,或者是在old list中在被移除之前保留多久。为提高并发性,可以配置更多的buffer pool 实例。不同的实例之间不会包含相同的数据
change buffer ,是buffer pool中的一块区域,用来存放那些还没有写入磁盘的数据。对于写入比较多的情况,change buffer可能会占用大量的buffer pool。对于读比较多的情况,change buffer占用很少,甚至可以关掉。它也可以配置为仅存储特定类型的变更以适应负载情况。
myisam 使用一种称为 key cache的buffer,它不存储数据,只存储索引。如果有多个key cache实例,它们也可以被独立地配置。
aria 使用一种和myisam的key cache很相似的被称为 page cache的buffer。aria的page cache对于固定长度数据格式速度更快。但是aria目前还不支持多个cache实例。
如果myisam或者是aria存储引擎作为主要引擎。那么key cache或者page cache应该设置为与需要经常访问的索引文件相同的大小。
表被打开时会被放入cache,并存储物理文件句柄(句柄是C++中的叫法,但是大致意思是通用的)。myisam和aria对每张表使用两种文件。(因为索引和数据文件是分开存放的)这种cache方式可以减少数据文件过热的情况。
当相客户端链接到服务器时,其对应的账户处于阻塞状态,那么主机的cache中会包含客户端连接到服务器的ip地址和主机名等相关信息,以在必要时阻断连接。
innodb数据结构
mariadb中,如之前所说,innodb就代表了xtradb。
innodb有一种称为表空间的东西,表空间是一种可以存放索引和表的文件。在老版本的mariadb和mysql中。所有的表都创建在system表空间中。如果 innodb_file_per_table 系统变量设置为1 (mariadb10版本之后的默认值),那么每张表都会存储在单独的表空间中。这个值是动态的。因此,你可以让一些表独立存放成文件,而另一些放在system表空间中。
system表空间默认情况会包含 innodb数据字典,undo日志,change buffer,以及 doublewrite buffer。数据字典是集合了所有innodb 表,列,和索引的元数据信息。system表空间以 ibdata 文件格式存放在 data目录中(默认两个文件)。
表空间的一部分叫做段,常规表空间包含一个数据段和每个索引的索引段。系统表空间有几个段。(原著并未继续说明)
page是一种存在于表空间或者buffer pool中的更小的存储单元。Page大小可调整。一个page可以包含一行或者着多行以及剩下未占用的空间。非空的比例叫做 fill factor(填充因数?其实就是page占用率)
在change buffer中被修改的page叫做dirty page(脏页)
在某些情况下,比如一致性读过程。innodb会连读读取几个page,总量达到1MB。那么这样的一组page叫做 extends。
innodb重点不仅是在读取,而是在锁机制,每条索引都相当于一个锁。
innodb 索引分为 clustered index(聚集索引?)和secondary index(二次索引?)主键是 clustered index。如果一个表没有主键,那么第一个包含非空列的唯一索引会作为主键列。如果连这个都没有。那么会自动创建一个隐藏的cluster index。所有的secondary index记录的叶子节点都指向cluster index 记录。因此我们说,secondary index包含了 clustered index
(实际上clustered inxdex 等同于oracle的索引组织表,而二级索引等效于常规索引,只是二级索引在检索时会通过clustered index作为一个跳板来定位记录。译者注)
认证和安全
mariadb认证基于用户名,密码以及客户端主机(或者是ip地址),用户名和主机构成了一个账户,如下+
user_01@localhost
每一个用户可以使用不同的认证方式,这在使用外部系统登录的时候额外有效。例如,操作系统账户。mariadb或者认证插件会根据客户端提供的密码来决定是接受还是拒绝连接。
权限可以配置给独立的账户或者是使用like通配符的一组账户。其中的一些权限可以允许执行特定的语句类型,或者是限制一类语句。权限可以应用到整个服务器,具体的设置对象可以是表或者视图中独立的列。这使得决定在什么账户可以操作什么对象这方面有极细的粒度和极强的弹性。
mariadb 10 同时支持角色功能,权限可以分配给角色而不止是账户。然后再给账户授予相应的角色。如果一个用户被授予了一个角色,那么他就有了这个角色所具有的权限。角色提升了批量用户权限管理的效率,同时它也是一种很安全的方式。
当然还有一些额外的选项,比如dba可以要求用户必须通过ssl来加密连接,dba也可以对用户做出资源限制,或者是觉得用户是否可以同时创建多个连接副本(也就是允许一个账户多次并发登录)。
information_schema database
information_schema 数据库(方便起见有时候叫做 I_S),是一个包含表信息的虚拟数据库。其库中的表可以分成几个类别。
1 metadata 元数据表:像 schemata,tables ,cloumns 包含了数据库的结构信息,比如表,列等等 2 status and variable 状态和变量表: GLOBAL_VARIABLES和SESSION_VARIABLES 表包含服务器的系统变量。GLOBAL_STATUS和SESSION_STATUS表提供了服务器的操作选项信息。
3 privilege 权限表:这些表以用户名为开头,后跟_PRIVILEGES,包含了每个用户对应每个对象的权限。
4 PROFILING 表:这种表用来监控当前会话中的执行的查询,还可以看到服务器的一些底层操作。
5 processlist表:这些表展示了活动的会话和他们的状态
其中innodb提供了几张信息表,有一些是xtradb特有的。这些表以 innodb_或者是xtradb_开头。
1 innodb lock: INNODB_LOCKS,INNODB_LOCK_WAITS,INNODB_TRX表包含了活动的锁,等待,和事物申请的锁或者是等待的锁。
2 INNODB buffer pool :这些表以INNODB_BUFFER_开头,其中包含了 buffer pool和页使用情况信息。
3 INNODB_METRICS :这些表提供了innodb的一些底层操作。
4 INNODB compression :这些表以INNODB_CMP开头,提供了页压缩的性能相关信息。
5 INNODB full-text :这些表以INNODB_FT_开头,提供了innodb表中关于全文索引的信息。
6 INNODB data dictionary :这些表以INNODB_SYS_开头,提供了innodb表,列和外键的一些元数据信息。同时也包含了关于文件的统计数据和信息(数据字典表,同oracle)
一般来说,这些信息可以通过查看 information_SCHEMA数据库或者是通过show命令显示。查看information_schema数据库更加灵活和标准,但是比较麻烦。
innodb的活动信息可以通过易读的格式展示出来,比如通过“show engine innodb status”,“show engine innodb mutex”语句
为了检索information_schmea数据库,服务器必须打开并读取数据文件,这些操作速度缓慢,因此,这种经常在生产环境中执行的查询最好只读取必要的文件,比如简单的方法就是加个where条件。
performance_schema 数据库
在mariadb代码中,关于性能监控信息的详细结果都会写入到一个特殊的数据库中———performance_schema。
但是,监控性能信息会导致服务器性能下降,再但是,你可以在控制文件中关闭它,通过设置 performance_schema变量为0
performance_schema变量基于以下几个概念。
1 actors 一个actor是一个监控线程,可以监控用户连接或者是mariadb的后台线程
2 consumers :consumers一种用性能数据填充的表
3 instruments : 指明了数据库哪些对象开启了什么监控。例如wait/io/file/sql/binlog等
4 object:是一些必须被监控的活动表
为了决定服务器如何被监控,可以设置 performance_schema 中的 setup表:setup_actors setup_consumers setup_instruments setup_objects . 当performance_schmea开启,且此时有底层操作任务发起的时候,如果开启了 actor,consumer,instrument和object的监控。那么新的信息将会写入到performance_schema中。setup_timer表用来决定监控项目的时间粒度(微妙,纳秒等)
performance_schema中的 setup类表由几张表组成,其中一些重要的表遵循命名规范,即基于前后缀。前缀代表了这类表可以提供什么信息,最重要的前缀比如这些:
events_statements_: 涉及sql语句
events_stages_:sql语句执行情况(比如解析和表打开)
*_instances_: 包含根据不同类型的锁进行分类的信息,比如 mutex_instances_ 就是互斥锁
events_waits_: 表包含了线程等待锁被释放的信息。
后缀标识的表拥有一些汇总数据,比如:
_current: 只包含当前活动连接
_history :仅存放一些受限的历史信息。
_history_long : 包含了更多的历史信息。
其他一些后缀,其意义见名知意。
例如,events_waits_current 表列出了当前线程的等待事件.events_statements_history 表展示了最近执行过的语句的相关信息。
比较mysql和其他DBMS:
每一个版本的mariadb都是基于对应版本的mysql修改而来。例如,mariadb 5.5基于 mysql5.5.当mysql的结构更新的时候,mariadb会修补其bug并且增加新的特性。mariadb完全兼容Mysql。也就是说,mysql上面可以用的sql语句,api调用,配置文件在mariadb上完全没问题。(并且保证结果一致),如果有什么未记录在案的部分,将会被视为bug。当然,mariadb会基于mysql代码来开发一些新功能,因此如果是mysql不支持的功能,可能在mariadb上可以使用,但是mysql就不行了。
在复制环境中,从mysql复制到mariadb是没问题的,只要版本号一样。但是!如果你使用了mariadb的新特性可能就不行了。
mariadb同时引入了percona server的一些特性。也就是说,如果仅使用percona server中的这些特定的功能,那么percona server也可以和mariadb很好的兼容。
这张表展示了mysql和mariadb的对应关系 每一对儿版本号相同的mariadb 和mysql 都是可以兼容的,并且最新版本支持向下兼容。mariadb 10.0 打断了这种传统,因为它只使用了mysql 5.6中的一部分特性,因此并不是完全兼容的。详细的兼容,不兼容列表请看 《mariadb knowledge base》。大多数情况下没什么问题,除非用户想在复制环境中把mysql 5.6和mariadb 10.0混用。比如 mysql 5.6能使用innodb表作为一种中间层去做memcache,但是这项特性在mariadb中还不支持。
mariadb和mysql使用 “可执行提示”(executable comments)来提高与其他dbms的 兼容性。可执行提示可以在mysql和mariadb上执行sql语句的一部分,但是在其他dbms上不行,也可能只能在mariadb,或者是最新的mariadb上。
比如 可执行提示可以是这样的: 如果加了 M 则这个命令只能执行在mariadb上 我们看到的都是简写的版本号,下面这个命令可以看到版本号是由5或者6位数字组成的:第一个或者前两个数字是主要版本号,随后的两个数字是次要版本号,最后两个数字是补丁号。例如: 需要注意的是,mysql 5.6上的 可执行提示在mariadb10.0上也可以执行,几乎完全兼容,但是到了mysql 5.7版本就不一定了。
mariadb 资源
主要的文档资源就是《mariadb knowledge base》也叫做KB。它包含了相关的开源工具,也是一个很好的交流技术的地方。 mariadb KB可以从这里找到:
https://mariadb.com/kb/en/
mariadb 基金会有一个博客可以让用户很容易地看到新版本的特性和一些其他重要新闻,地址如下:
https://mariadb.org/
planet mariadb聚集了mariadb的相关信息
http://planetmariadb.org/
JIRA工具可以用来报告bug,浏览已知bug和修补情况信息,也可以浏览新发布的版本将会修补什么bug和带来什么新的特性。地址如下:
https://mariadb.atlassian.net/bnrowse/MDEV
总结:
在本章中,我们大致浏览了mariadb的架构。解释了很多的mysql客户端的特性。这可以提高DBA的工作效率和减少GUI工具的使用。
我们讨论了mariadb中引入的存储引擎,别管是二进制还是源码。其中包括了innodb,tokudb,myisam和aria。innodb是本书主要讲解对象,并且实际环境中也是大量使用,由于这种原因。我们着重研究这个引擎和其数据结构。当然,可能由于一些其他原因,在某些情况下使用其他存储引擎性能更好。还会介绍一些比较小众的存储引擎,主要是可以解决一些特殊需求。比如以后的章节中我们会讨论sipder引擎。在复制章节,我们还会涉及到BLACKHOLE引擎用来避免复制某些数据。
我们简要地讲了一些关于log的内容。其中有一些是使用mariadb特性必不可少的。比如物理备份,复制,误删后的还原。关于如何使用,我们会在随后的章节详细讲解。log在实际环境中必不可少。
最重要的概念就是cache和安全。后面会有一整章来详细讲解
information_schema和performance_schema数据库包含了大量有用信息。本书不会详细讲解其全部内容。想看的从KB里找。当然,在随后的章节中,有些表会涉及到这两个数据库,我们也会通过举例来说明在实际环境中如何使用。
mariadb是mysql的衍生产物这个我们已经讨论过了。这个话题中最重要的莫过于在复制环境中用mariadb替换掉mysql时候可能产生的问题。或者是开发人员想开发一个可以用在这两个数据库上的应用的时候(比较特殊情况时开发人员想使用mariadb的唯一特性)
本章的最后列出了一些比较有用的mariadb相关资源。所有的高级用户都应该经常保持良好的学习状态,跟着时代的步伐前进,学到脱离这行为止。
在下一章中,我们将会学习如果使用日志来找出mariadb中的一些错误以及如何调试sql语句。这些知识对于排错尤为重要。并且在随后的章节中我们将会使用这些知识来处理一些复杂的情况 第二章 错误调试
本章我们将会讨论一些关于mariadb服务器和sql相关的排错知识,以下是本章涉及的几个工具和技术要点。
1 错误产生原因
2 诊断区
3 错误日志
4 一般查询日志
5 日志维护
6 SQL_ERROR_LOG插件
7 存储过程排错
理解mariadb中报错原因
在探讨排错技术之前,需要先搞清mariadb中通知用户错误产生原因的工具(可以理解为mariadb内置组件)。
mariadb中的错误告警由以下内容组成
1 SQLSTATE 值
2 error 编号
3 error 信息
ERROR一般是由服务器报出来的,用户可以使用signal和resignal异常函数去处理
为了获得ERROR信息,mariadb提供了3个C语言接口 mysql_sqlstate(),mysql_errno(),mysql_error(). 几乎所有的mariadb或mysql都有其对应的api接口。这些方法和语句将会在本章的随后部分讨论。现在,先让我们看看mariadb中的ERROR。 SQLSTATE 值
SQLSTATE值是一串5位数字。前两位代表了错误类型和关于错误的一般信息。后三位代表了ERROR的子分类和具体的错误编号。如果子分类部分数字是000,那么代表子分类无法确定。所有字符都是数字或者是大写英文字母。
特殊值——00000 代表了成功。这是唯一的一个00类,并且这个值不能被用户提出。01代表产生了一个请求操作的警告,但是其中的问题的一部分已经被跳过。02代表找不到,严格来说它不能算一个错误。反而还有可能是用户希望的,比如用游标检索行,当它读取到最后一行的时候,就会产生这个错误。
其他情况下,如果错误发生,那么相应的执行就会被中断。对于非事务型引擎来说,这就意味着事物只执行了一半。如果你尝试一次插入两行,第二行包含了和某个主键重复的值时就会中断。
就当前mariadb版本而言,有些情况下会有一些无法确定错误。它们统一使用HY000代码。也叫做general error. 此值代表从MySQL继承的许多ERROR,以及大多数MariaDB特定的ERROR
error 编号
error编号或者说是代码,是一个smallint 类型的signed 值(最大为32767),用来唯一确定一种错误情况。0代表了成功,不能被用户当做异常抛出。
在同版本号下,mariadb和mysql的error编号基本一样。mariadb特定的编号从1900起。这种特有的错误编号主要和mariadb的专有特性有关,比如虚拟列或者是动态列。
error 消息
error消息是有一定可读性的长度为varchar(128)的字符串。在最简单的情况下,不需要查看mariadb文档,仅通过error消息就可以知道具体错误原因。有时候也会额外包含一些因为失败操作而涉及到的一些表或者列名。
通常情况下,error消息足够说明到底发生了什么错误。然而,如果error消息太难懂或者容易产生误解,又或者是问题无法很快解决,那么用户可以通过error编号到mariadb文档中查看详细内容。
自定义error
自定义错误是用户通过signal或者是resignal异常处理函数明确产生的。例如,如果存储过程中输入了一个不正确的参数,那么就会产生一个错误来表明问题原因。比较好的是,signal函数可以用在存储过程之外,因此它可以用来往SQL_ERROR_LOG中写入错误,这些消息对于DBA或者是日志读取工具来说是很有意义的。
用45000这个值来作为用户自定义错误号还是很推荐的。mariadb,mysql和其他相关衍生数据库现在以至于到将来的某一版本都不能使用这个值。说回来,任何子类值为000都是安全的,因为这样的值与具体的错误无关(也就说不管主类编号如何,只要子类编号为000,就是安全的)。由于这种原因,这种错误也是可以被接受的。 //* 关于mariadb详细错误代码,请看《mariadb error codes》,网址https://mariadb.com/kb/en/mariadb-error-codes/ *//
There could be valid reasons to use different SQLSTATE values; for example,
a custom error in the 01 class is not fatal, and continues the execution. Another
reason is mapping a custom error to a built-in error; for example, a custom error can
be raised in a particular case when a duplicate key error occurs. A custom error is
created to provide the DBA or the applications with useful information on how to
debug the problem. However, you also probably want the application to take actions
that it normally takes when a duplicate key error occurs. So, the custom error can use
the same 23000 SQLSTATE value. //*The default error number depends on the SQLSTATE value: 1642 for
warnings, 1643 for not found conditions, and 1644 for errors. These
values are dedicated to user-generated errors. In these cases, the default
error message informs whether the condition is a user-generated warning
or an error. Otherwise, the default error message is an empty string
*// Other condition properties exist in SQL standard and are partially supported in
MariaDB. Such condition properties contain additional information about the cause
of errors. They can be set for custom errors via SIGNAL and RESIGNAL, and they can
be read via GET DIAGNOSTICS. However, these properties are not set for built-in
conditions and are never returned to the client; so, developers normally ignore them show warning 和show errors 语句
mariadb的ERROR,WARNING都存放一个称为 diagnostics area 诊断区的地方。通常情况下,诊断区包含了最近执行时产生的错误。但是关于诊断区具体的工作机制,包括如何存放,删除错误信息将会在下一节讲解。mariadb提供了一些sql语句允许我们来查看诊断区的内容。
show warning 语句返回诊断区中当前产生的警告。show count(*) warning语句返回了警告数。这个值取自于warning_count 会话级变量。
如下图,我们展示了执行了一个可以产生两个警告的语句 输出包含三列,错误代码(CODE),错误消息(MESSAGE),以及错误级别(LEVEL),错误级别代表了错误产生的种类,其可以是note(一般服务器消息),warning和error。如果你设置了@@SQL_NOTE=0,那么note消息不再显示。
有些sql语句和系统变量可以导致错误级别变化。比如使用 if exists和IF not exists选项时。它们本该产生error错误,但是通过加上这两个子句,错误级别就改变了。
下面的例子展示了上述情况。(其实就是加上了异常处理功能) 上面的例子中,我们执行了两个非常相似的语句,首先我们使用了drop table删除了一张本不存在的表,因此产生了error。随后我们使用if exists选项来删除表,这就意味着如果表本身不存在,那么mariadb不会产生error,但是warning还是有。因为你可能需要知道这张表本身不存在。这个例子向我们展示了level列的重要性。show warning语句会返回和这种情况非常相似的结果,可能唯一不同的地方就在 本该是note的level,现在是warning。
正如之前所说,dba可以决定是否把警告写入错误日志。sql子句可以改变错误记录行为(比如使用if exists,if not exists或者是在dml中使用ignore)或者通过设置sql_mode系统变量来决定是否记录更多可能出现的问题。如果你认为有些错误你不怎么关心,对系统也没啥太大影响,设置的稍微不严格一些可以让你的日志文件更小。
show errors 和show count(*) errors 语句和之前讲的非常相似,不过它只能用来显示和统计errors,不会显示warnings或者notes。关于诊断区中errors的数量可以通过会话变量 error_count来设置。
下面的列子展示了show count(*) warnings 和 show count(*) errors之间的区别 正如上例,尝试通过if not exists子句创建数据库,实际是存在的。因此,errors数为0,warnings 为1。 diagnostics area诊断区
诊断区分为两个子区域:statement信息和condition信息。
statement信息包含了两个值:
1 number:这个值代表了存储在condition区域的condition数量
2 row_count: 被修改的行的数量。这个值可以通过 row_count() 函数和mysql_affected_rows() 接口返回。
写入和删除诊断区内容有着准确的规定,了解这些规则对于处理问题非常有用,可以避免你走入排错的陷阱,同时对于存储过程排错也是非常重要的。
无论如何一条语句都会生成至少一个状态信息(note,warning,errors),任何新出现的condition信息都会使得之前的信息被删除。然而有一个例外就是如果新的语句使用了resignal 或者 get diagnostics。那么之前的状态信息不会被删除。这使得开发人员可以在语句执行失败时做出处理。如果语句由于语法原因不能被正确解析。mariadb不知道 resignal还是get diagnostics,那么此时诊断区会被清空
如果一条语句不产生任何信息,也不访问任何表,那么诊断区中老的条目得以保留,如果访问了表,则老的条目会被删除,即便语句本身不产生信息。
max_error_count 系统变量代表了诊断区中可以存放的最大的消息数量。
现在,让我们看看诊断区是如何工作的,在下面的例子中,show warnings命令是用来查看整个诊断区最简单的方法。通过设置SQL_MODE服务器变量为错误的X来制造一个ERROR。
下例中,我们先生成了一个错误,诊断区中会包含这个错误信息。 当我们看见了诊断区这个错误之后,我们执行一个正确的不访问任何表的语句。没有错误产生,并且诊断区中还保留原来的信息。
下例中,我们用set来生成一个错误,随后再用drop table生成另一个错误。 上面例子中,由于drop table访问了相关表,诊断区被清空,因此show warnings只显示了第二条错误。
下例中,我们先生成一个错误,然后执行resignal生成另一个错误。 如你所见,即便resignal语句正确地执行了,也不会清空诊断区,show warnings依然会返回两条错误。再看下面的例子。我们先生成一个错误,随后执行一个正确的语句 如你所见,即便第二条语句不产生任何警告,但是诊断区还是被清空了
GET DIAGNOSTICS 语句
GET DIAGNOSTICS语句是一种不错的用来展示诊断区结构的工具,因为它会把每一个值copy到一个变量中。
它的语法十分冗长且容易输错,但是它也是唯一一种可以在存储过程中分析诊断区的方法。事实上,show warnings 给客户端返回了结果集,但是不允许通过sql代码访问它。当具体的错误发生时,HANDLER会阻止语句继续执行,但是又不提供相关的问题信息。
因此,可以通过GET DIAGNOSTICS命令来展示诊断区中的内容。
先让我们往诊断区中填充两个错误。我们使用之前sql_mode=x的例子 现在使用get diagnostics来吧诊断区中的消息数变拷贝到一个变量中,然后使用select来查看。如下: 现在我们知道它包含两条项目。然我们看一下 SQLSTATE值,记录的数量,和记录具体的内容,代码如下: ERROR LOG
错误日志包含了服务器启动,关闭过程中产生的错误。其包含了比如停止服务器或者在启动中何种插件被阻止跟随启动以及数据错误。日志可以打开或关闭。
//*日志默认存放在data文件夹中的文件。默认名为 主机名.err .在复制环境中的不同主机名之间使用默认日志名称稍欠妥当,DBA应该专门去起个新名字。 *//
windows中,如果没特别设置,错误日志是默认开启的。errors可以通过在控制台通过 --console选项显示。输入 --log-error无效。
//* linux和unix系统中,错误日志默认是关闭的。但是错误日志还是有的,它会被写到标准输出上,除非你用了nohup 之类指定了特殊的文件 *//
在所有系统中--log-error是用来开启日志的。它可以指定日志文件名称和路径(路径可选,不写默认放在data文件夹中)语法: --log-error=filename .
设置完毕后,路径和名称可以通过 log_error 服务器变量读取,只读。
//* windows下可以使用控制台选项关闭,但不适于linux。如果多个服务器实例使用一个配置文件,那么只要有一个实例用它,它就不能被关闭。另一种曲线救国的方法是把日志写进/dev/null 中 *//
如果 log_warnings 变量大于0,则warning也会写入到error 日志中。如果这个值大于1,所有的连接错误也会被记录下来。
error log 格式
错误日志一行一条。当服务器启动时,在最后一次关闭消息之后留下空行。
看个例子 首先我们看到的是错误发生的时间。以 YYMMDD格式记录。
随后是一个空格和错误类型。其可以是[Note] [Warning] [ERROR]代表了show warnings命令显示的不同的错误类型。如果[]里面是 mysqld_safe,意味着此条记录被mysqld_safe 脚本记录,这样的行是关于服务器启动和自动重启的内容。
有些不必要的信息不会遵循这样的格式,比如在服务器崩溃后的启动中。这样的信息绝不会写入系统表。
有些系统中,异常关机会导致error log包含大量跟踪信息。这可以用来处理服务器问题。
通过error log 排错示例
如果你linux系统上的mariadb服务器起不来,第一件应该做的事就是去看看error log。最新产生的error会在日志的最下方,那些带[error]标记的就是。
你可以通过命令行在 error log中搜索带有 error 标记的最后10行,其中涉及到两个命令 tail—返回文件最新产生的行,grep—返回你匹配到的字符串。如果没有权限,你可以用sudo来获取error log的访问权。这些命令都是GNU 项目的一部分,几乎所有的unix类系统里都有相似的工具。如果要在windows中使用这些命令,你得需要一种叫做 Cygwin的东西。
开始执行试试: 第一行就告诉了我们具体原因所在,你在配置文件中设置了一个错误的变量(正确地应该是basedir而不是base_dir),第二行只是告诉你服务器要关机。但是显然你应该已经明白该怎么做。去配置文件中修改吧。
这个例子很简单,但是要举一反三,此处的重点是查看error的步骤和问题解决的思路。
system logs
mysqld_safe有一个 --syslog参数,这个参数会把错误记录到系统日志当中,除非你系统有日志程序你才能这么用,我们会展示一个例子,一般在linux上这么用。具体取决于日志记录程序,一般情况下系统日志中都会带有 mysqld 或者是mysqld_safe这样的标记来表明日志来源。如果你服务器上安装了多个mysql或者mariadb实例,那么可以设置多个前缀来区分日志来源于哪个实例。下例中使用了--syslog-tag选项来给服务器实例打个标记。 这样,错误就会带着一个 mysqld-serv1和mysqld_safe-serv1标记被写入系统日志。通常这些参数是写到配置文件当中的。且只能写到[mysqld_safe]下面。如下: windows系统中没有syslog这个东西,但是日志会被写入到windows事件管理器当中。事件来源是以服务或者mysql来命名。warnings,notes和信息条目也不会遵循标准的日志格式。
general query log 一般查询日志
所有发送到mariadb的语句都会根据接收顺序依次记录到 general query log 或者 general log中。这个顺序绝不会和多线程环境的执行顺序相等(因为语句通常会等待锁释放,此处说的意思是日志顺序是确定的,不会产生混乱和冲突)。连接和断开也会写到general log中
general log一般用来找出应用程序bug导致的问题。
general query log依赖于binary log 格式.这个日志将会在第八章备份和灾难恢复中详细说明。general log设计出来是为了方便人们阅读,binary log是方便程序读。但是我们可以通过mysqlbinlog工具来读取其二进制内容。这个工具会根据日志格式,不管是statement,row还是mixed来跟踪数据库的改变。你可能现在不明白,没事,随后的章节就会讲到。但是需要注意的是,general log只在binary log 使用statement格式时有用。如果使用了mixed格式,那么有些语句就不会被记录进去了。
可使用--general_log启动选项来开启general log,关闭设置为0即可。默认是关闭的,但是这个变量是动态的,可以随时开启关闭。
如果开启了general log,默认存放在data目录下,名字为 主机名.log 。如果想指定别的路径,需要设置 --general_log_file=filename 参数。在复制环境中,推荐给每个服务器实例的general log专门指定一个唯一的名字 --log-output 选项决定了 error log和general log的写入位置(随后即谈)。要记住的是这个变量是影响这两个日志。不能单独设置。有三种选项FILE(日志记录到文件中),TABLE(日志写入到数据库表),NONE(记录行为被抑制),如果想既写入表又写入文件,如下: 如果有NONE选项,其他值被忽略。
此变量为动态变量。可以再运行时更改。为全局变量,因此不能针对当前会话单独设置。
有时候,超管用户想关闭他自己的日志记录行为。最可能的原因就是他想改密码(密码会被清晰地写入文件或者是表)。虽然不能针对每个会话设置日志输出位置,但是可以设置是否记录,如下: 这个变量既可以在全局设置,也可以在会话中设置。因此如果有必要可以关闭所有的日志记录行为。
文件格式的general query log
general query log 以下面的样式作为开始 服务器运行一段时间,经过一些查询操作后,这个文件头部变成了4列,其下有很多行,每行包含了服务器何时启动,何时日志被刷写(日志刷写随后讲解)
如下: 日期列,以 YYMMDD HH:MM:SS 显示时间,通常这个值可以被忽略。
ID列,包含了连接的ID号,这和记录在information_schema和performance_schema中的一样,由connection_id()函数返回。
command列包含了用户的操作类型,可能值是:connect,int db,query 和quit
argument列的具体内容根据command不同而不同。在之前例子中,用户发出了一个语句,那么argument列就包含了这个命令的文本信息。
来看另一个例子: command列中的connect代表是用户一个连接到服务器的行为,argument代表了具体的账户
再看一个例子: init db代表了用户选择的默认数据库(通过输入 use语句)。argument列说明了具体的数据库名称。
再看一个例子: quit 代表了用户的退出行为。如果quit行为没有具体command内容,那么可能是连接还处在打开状态或者是其他线程关闭了这个连接。
general_log 表
正如之前所说,general query log可以写入到数据库表中,表名叫做 mysql.general_log。它包含以下几列。
1 event_time:等效文件中的time列
2 user_host:执行语句的账户
3 thread_id:等效文件中的ID列
4 server_id 代表复制环境中的server id
5 command_type: 等效文件中的command 列
6 argument:等效文件中 argument列.
下面我们展示一下表中的内容: 上例语法同样适用于slow_log表。
mysql.general_log有几个限制,最重要的限制就是不能被用户通过dml语句修改。强行修改会报错。这样的表也不能被锁住。FLUSH TABLE WITH READ LOCK 可以安全执行的原因是它仅锁住了其他所有表。在这些表上使用 LOCK TABLES会出现错误。
general_log表是一个CSV表(由逗号分割)。这张表允许用户通过其他第三方程序直接打开其对应的数据文件(general_log.csv 存放在数据文件中),因为CSV是一个被广泛支持的文件格式。但因为CSV不支持索引,因此会减慢sql查询。
general_log表也可以使用MYISAM格式,快是能快点,但是还是不如直接写入文件快。其他的存储引擎就不支持了。记住在你更改这张表的存储引擎之前,你应该先关掉日志,操作如下: 如果你不关心日志表中旧的日志内容,你可以通过 TRUNCATE TABLE 命令进行清空。但是DML语句不行,也就是说你想删除部分内容是不行的。因此,循环使用日志是一个防止日志表增长过大的方法。RENAME TABLE 命令就是一种行之有效的方法。但是在重命名表之前,你得临时关掉日志。关闭方法如上。
像是 CHECK TABLE,OPTIMIZE TABLE,和REPAIR TABLE 命令都是支持的。修复操作速度会很慢,如果表损坏了,在修复之前,还不如重命名后重建一个新的,这样也不会影响日志记录行为。
如果你使用ALTER TABLE给转换成MYISAM殷勤的日志表加上索引,其相应的select操作就会更快,但是也会减慢insert操作,如果你使用日志循环方法(就是那个重命名表的),你可以给这些历史日志表加上索引。索引创建速度依据历史表大小有所不同,可能非常慢,而且还需要占用一定空间,但是查询速度会快很多。
使用general query log 排错示例
以下两个例子展示了通过SQL 表来展示 mariadb中可执行的交叉平台的代码。并不意味着在表中存放日志就是个好方法,默认的方式是存放到文件,DBA们也应该仔细斟酌之后根据具体情况做出决定。如之前error log 部分向我们展示了如何使用linux命令来查看这些log文件。
第一个例子,假如你发现有人删掉了一张还在使用的叫做 历史订单的表。而且还发现好在删了还没超过一星期。那么第一件要做的事就是还原表。这个我们在第八章备份和灾难恢复再说。然而,这张表很有可能会再次被删掉,你当然不想这种事情再次发生。这很可能是一些权限或者是程序BUG。为了找出具体原因,比如谁删了表,什么时间。那么这个SQL就可以展示其具体内容: 如上代码,第一个条件限定了event_time 必须不能超过1周,第二条件什么也不做(第三个条件将会满足),这将会以一种很快的方式过滤掉很多记录。第三个条件是最有意思的。你可能不知道服务器收到的具体字符串,比如它可能包含空格,数据库名,或者表名两侧的引号,此时可以使用%通配。只要包含你指定的内容的行都会返回,哦!别忘了单引号中是区分大小写的。
由于可能会返回到很多结果,虽然不可思议,但谁又说的准呢。因此,你应该这么处理这些结果:最新的行一定是最近删除表的那个语句,而且,你肯定还很有兴趣看看到底是谁还执行了这个相似的语句。我们将把结果集排序后限定在20行。
紧随第一个方法,接着我们可以这么做:我们发现id =100的连接在早上执行了drop table 语句。为啥?这连接还执行过别的什么没有?我们很想知道。除了你本身的好奇心,还因为查看执行历史可能会帮助你找到网站应用中sql语句的bug。基于这些因素考虑,我们执行如下命令: 以上就是使用general log 的常见用法,当然它还有更多灵活多变的方法提供更详细的信息。比如所有客户端一旦不再需要连接,那么就应该自动关闭以释放内存,然而此处问题在于,mariadb依然会保持一些线程buffer和临时MEMORY表,直到某一个连接断开。这种情况就造成了内存浪费。这时候可以使用 general log来查看哪条连接没有使用QUIT命令退出,也就是说查一下哪些连接没有正确关闭。记住,如果一个连接现在关闭了,但是它从没执行过QUIT命令,那么这个连接很可能是被root用户用kill命令关闭的。也有一些连接可能是因为会话超时被关闭的(使用了@WAIT_TIMEOUT服务器变量)。换言之,应用程序创建了它们,却没关闭它们。因此它们很长一段时间都处在非活动期,浪费内存!例如我们执行这样的查询 这个实验需要创建一张新表,如果你创建了索引查询会非常迅速。然后你可以执行两次后别退出,就在那里耗着。检索这张表的时候,输出如下: 输出非常容易理解。通过线程id来存储。如果同一个线程id没有QUIT命令,或者count(*)比匹配到CONNECT命令的数量还少。这就代表了线程没有被顺利关闭。除非服务器重启,线程id在同一天中几乎不可能出现重用的情况,因此你count(*)的值可能是1或者丢失。在之前例子中,11和12线程从未发起QUIT命令,因此我们想知道这些线程怎么了。
下面的命令返回了使用这些线程的账户: 比如这个例子,general_log左连接 tmp.g_log,找出g_log中那些线程id为空的quit记录。手快点,别在下次服务器重启后再执行上面的查询。具体账户如下: 现在我们知道到底是谁做了这些事,如果是应用程序调用的,那么就把开发喊过来给他两脚。 服务器日志维护
所有的服务器日志都需要维护,我们将从FLUSH这个偶尔会用到的用来把最新信息写入日志的语句入手。然后我们会讨论如何搞循环日志(包括文件格式和表格式)以释放磁盘空间。
刷写日志
在当前操作之后或者操作备份之前,没准你会用FLUSH把相关内容刷写到日志中。刷写日志意味着文件会被关闭然后重新打开,所有的缓存的信息都会在这个过程被写入文件。就基于文件的日志来说,FLUSH LOGS 语句就可以完成。为了刷写所有日志,执行以下操作: 下面一些带参数的命令可以只刷写一种日志: 默认情况下,这些语句会被复制环境中的从端也复制过来。如果只在主端执行,需要使用LOCAL关键字(或者NO_WRITE_TO_BINGLOG,同步),代码如下: 注意,如果使用了general_log和slow_log表,这些命令对它们不起作用(指的local关键字)。刷写所有表使用FLUSH TABLES 命令即可,这个命令会关闭数据文件然后重新打开他们。还会强制把缓存的变更写入到文件中。然而没有哪种命令能只刷写一张表,所有数据库中所有的表都会被刷写,命令如下: mysqladmin 工具也可以刷写日志和表,命令如下: 基于文件的日志循环
mariadb不会自动循环日志
唯一例外的就是binary log,它会在到达指定大小的时循环。同样,当刷写binary log的时候,也会自动创建一个新文件。binary log自动循环我们在第八章备份和灾难恢复一章讲解。其他那些到达指定大小的日志如果想要循环只能通过用户自己周期性地去操作,RED HAT 企业版和其衍生的linux发行版提供了实现自动日志循环的工具。
让我们来看看如何使用linux shell来重命名general log 文件吧。
首先你得获得ERROR文件的列表,大小以及一些相关数据。假如我们把当前文件叫做 maria.log.01,对于不再使用的历史文件,通过后缀来区别(比如 02,03),示例如下: 如果显示以上结果则意味着mariadb的配置文件中会包含这样的选项: 现在你想要重命名这个文件,服务器正在运行,第一件需要做的事就是关闭日志记录功能 删除最早归档的文件然后依次重命名其他文件,如下: 接着开启日志记录功能: 现在,刷写日志: 如果maria.log.01文件不存在,服务器会自动创建这个文件。保险起见,我们使用以下命令检查一切是否如预期: windows下,删除文件命令是del,重命名是rename,获得当前目录文件列表命令为dir。因此对于windows平台,正确命令应该是这样的: 当然不应该在生产环境中手工进行这些操作,用这些脚本之前起码都应该测试过确认没问题再用。
RED HAT linux企业版里面的完成日志循环的命令叫做: mysql-log-rotate
基于表的日志循环。
正如先前提到的,基于表来做日志循环没有问题,同时基于表的日志查询比较慢。为了实现日志循环,我们需要创建存储过程,这个过程通过事件调用,在本节后面有描述。
让我们看以下代码然后讨论它如何工作: 由于所有的过程必须属于一个数据库,因此我们建了一个叫做 “_”的数据库用来存放程序。数据库名字短点儿能让你少敲代码。
事件的第一个动作是删除最老的日志表。随后,通过rename命令更改了日志表的名字:general_log变成了general_log02.general_log02 变成了general_log03.由于rename table命令是一个原子操作,因此如果一个重命名失败了,那么所有的重命名操作都会失败。最后,过程使用like语句,创建了一张和general_log02结构一样的 general_log表.
general_log在过程的一开始被关掉。然而有可能general_log一开始就是关闭的。因此我们把gerneral_log变量copy到一个临时变量中,然后在程序的最后还原它。
这个过程非常简单。如何写好一个过程并非本书范围。但是要说的是一个好的过程是灵活多变并且带错误校验的。读者在编写过程中可以遵循以下几个思路。
1 归档日志的数量不应该固定,应该从表中读取。因此,rename table命令不应该写死,应该是由字符串拼出来然后通过 prepared 方式执行(听起来很高大上,但其实用的非常多,因为sql本身并不灵活)
2 语句中的每一部分(old_name to new_name 部分) 应该判断一下仅在原表存在的时候执行。因为没有理由这张表不存在,我们必须记住,如果找不到,那么整个循环操作就会失败。当然,drop table语句中也可以用 if exists 子句判断一下。
3 应该给用户返回一个是否执行成功的结果,如果在事件中调用该程序,可能会导致出现错误(因为事件不能返回结果集);因此,你可能需要把结果记录在一张表里。
写出这样的过程代码是非常有用的,1是更容易排错,2是可以随时手动调用。然而,你可能希望周期性地自动执行,这时候,你需要写一个叫做 例行程序的事件。(等同oracle 中job,自动任务,crontab等),代码如下: 这样的自动任务应该在服务器负载并不繁忙的时候执行,比如之前的例子,我们把它放到周日和周一的0时执行。
SQL_ERROR_LOG 插件
error log中存放着SQL的错误,把这样的错误日志分开存放对于应用程序排错是非常有用的。此时可以通过mariadb 5.5中引进的插件SQL_ERROR_LOG 来完成。
插件默认不安装,可以通过下面命令安装: //* 安装一次即可,重启无需再安*//
mysql 用户可能注意到了我们使用了mariadb中的一个扩展语法——INSTALL 语句。它可以安装存放在sql_errlog中的所有库,并非只能安装这种,只是在目前,我们只有这一个库。唯一的优势就是命令更短。当然也有不同的地方:不需要包含文件扩展名(.so或者.dll),这些由mariadb来操作。这使得命令独立于系统存在。(mysql中应该也有类似的功能,我并不是很了解mysql)
如果安装了SQL_ERROR_LOG,系统就会多出几个变量来控制日志行为。先让我们看看这些变量是啥,命令如下: 如果卸载了插件,对应的变量也就没了。
先让我们描述一下你可能用到的变量:
1 sql_error_log_filename :log 文件的名字,存放于data文件夹,此变量只读。
2 sql_error_log_rate: 代表了在错误写入log文件之前可以缓存多少个错误。1代表立即写入。10代表了发生10个错误再一起写入。0代表关闭日志。在sql错误频发的系统中,调大这个值对磁盘压力有很大的缓解。
3 sql_error_log_rotate:为OFF,开启为ON(或者其他允许的值)强制日志循环。
4 sql_error_log_rotations:代表在日志循环过程中可以保留多少个历史归档文件。9代表可以保留9个归档历史文件(不含正在使用的)。此变量只读。
5 sql_error_log_size_limit:单个日志文件允许的最大体积(单位 b).一旦日志达到了这个体积,就会使用新的日志。此变量只读。
循环操作在日志到达sql_error_log_size_limit指定大小或用户设置了sql_error_log_rotate时发生。旧文件和当前文件名字相同,只不过后缀名通过数字扩展以区分开。 强制循环对脚本或者存储过程排错是非常有用的,这使得我们不用读辣么大的文件而只是找其中几个错误就够了。
注释用他们所属的语句记录,然而,如果为优化网络流量,客户端把语句发送到服务端之前再去清除提示是不可能的。除非使用 --comments 选项特殊指定,mysql命令行客户端才会剥离注释。记录注释使搜索语句更加容易。例如,假设你不确定给定的语句在某些情况下是否产生了错误。你可以添加唯一的ID到你的语句中,如下: 然后你就能搜索到包含你指定内容的行: SQL 错误日志设计出来不是给程序而是给人读的。比如这个例子: 如上,包含接收到语句时的日期,时间,发起语句的账户,以及发生的错误,
存储过程排错要点:
mariadb 中的存储过程排错可以说相当不好整。原因很多,但最明显的就是mariadb没有任何原生的debug接口。有些排错人员使用一些烂招式来模拟出排错过程。比如分析过程代码,往里面加间断点,或者跟踪堆栈调用或者给他返回一些信息。这种招式会改动大量代码,因此不大可靠。其他一些排错人员在内部执行代码来实现排错功能。然而他们不能在mariadb和mysql的任何时候再现出准确的动作。尤其是,如果你用的不同版本的数据库,其实他们肯定是有程序bug或者文档中有错误的地方。当然,我不是特指谁,在座的各位软件都有BUG。
为了调试存储过程,开发者需要使用一些小手段,比如通过在过程代码中加入一些信息语句。但是记住,如果过程调试完毕,相应的语句一定得删除或者注释掉。
假如开发人员只是想知道if的分支语句是否执行了,那么select 1 就足够。比如这样 大多数情况下,在执行动态prepared 语句之前。开发者可能想通过查表中的一行或者变量来检查值是否正确。校验文本信息通常是比较靠谱的做法。比如这样 然而,基于select的排错方式仅适用于存储过程,因为其他程序(比如函数,触发器,事件)不能返回结果集。就开发层面上讲,通常是通过存储过程实现功能。
对于事件比较简单:可以不加修改就把事件转换为过程。然而,事件通常不管执行是否成功都可以对表写入数据。这种机制在某些情况下非常有用,比如说产生了错误,又什么都没给客户端反馈。
对于函数还比较容易。但是函数比过程多了很多限制,所以最重要的是记住哪些功能不能在函数上用(比如,prepared语句),唯一需要修改的地方是过程不能返回值,因此RETURN语句需要转换成select语句。
对于触发器,转换工作就很难了。因为触发器可以访问记录中的OLD值和NEW值。在一些简单情况下,转换成过程需要的参数是个方便的解决方法。然而在复杂情况下,这种方法容易出错,因为值必须通过手动传递。比较好的解决方法是使用insert语句替代select语句。值不会返回给客户端,但是它可以写入到debug表中(其实是自己创建一张用来调试的表)。但是这种曲线救国的方法因为多了写操作会增加代码量。
通过 SQL_ERROR_LOG插件调试程序
SQL_ERROR_LOG是个用来调试程序相当有用的插件。例如下面的过程代码: 上面这个过程很简单,就是清空backup数据库里的表之后,copy一张需要备份的表到backup数据库中。通过 TRUNCATE TABLE 和INSERT...SELECT 语句就能很容易的看出来。
然而这个过程却隐含着很多问题,比如,backup表是否存在,源表是否存在,表结构是否改变了?或者有权限问题?其中任何问题发生都会导致SQL 错误。这种情况下,执行会直接跳到DECLARE EXIT HANDLER 语句块,除了抑制错误之外什么也不做。这个语句块看起来好像没什么用,然而在真实环境中,有好多理由去使用HANDLER语句块。并且它会抑制一些错误(除非使用RESIGNAL语句).当在调试程序时,这就意味着错误不会返回给客户端,并且开发者也不会注意到已经发生的错误。
在程序增加一个空的HANDLER是很有用的。它可以抑制一些你意料之内,并且又不会产生问题的错误或警告。比较有代表性的例子就是当存储过程通过IF EXISTS语句试图创建一张表的时候。如果表存在,就会产生一个note。然而在大多数情况下,它不仅没用,而且还很烦人,比如你想在 EXIT handler中抑制note,那不行,程序依然会退出。在continue handler中,程序依然会正常执行,警告或错误会不明不白地忽略掉。虽然这可能是所期望的行为,但它也是一个潜在的问题:比如我们使用SQLEXCEPTION这样的通用类,handler就会抑制错,这当然不是我们期望的,这样会使调试更麻烦。
使用show warnings 命令是一个快速查看错误的方法。但是这样的语句只能用在过程,而不能用在不具有返回结果集能力的其他程序中。
使用SQL_ERROR_LOG 插件是一个查看错误的好方案,比如: 执行这个过程的时候,我们看不到有错误报出,但是我们想确认是否产生了错误。如果我们查看SQL error log的最新行,我们能找到大概如下条目:
如果backup 表不存在: 如果原表结构改变: 如果原表已经被删除: 如果每件事都做的正确,我们当然看不到什么错误。
总结:
本章主要讲了日志查阅,记录以及理解mariadb中的错误。
我们通过诊断区中的一行错误信息来获得系统运行的可用信息。我们讨论了诊断区填充清空的机制。以及获取诊断区的一些命令,比如 show warnings,show errors 和 get diagnostics. 如果SQL_ERROR_LOG插件启用,那么SQL错误也可以记录到文件中。我们还探讨了如何使插件更高效工作。有些特性关乎到程序调试,这是一项比较有难度的任务。
general query log对于应用程序调试和找出问题所在也很有用。当不产生任何错误信息的时候,它可以跟踪所有我们发送到服务器的命令。这样的日志可以被写入文件或者general_log 表中。
SQL错误不是唯一可能发生的错误。error log包含了其他类型的错误,包括服务器启动失败,或者插件加载,数据损毁问题。这些都会被记录到文件中。
我们还探讨了 error log和general query log之间的格式。通过举一些简单的例子,使用linux命令行在日志中找到我们需要的条目。我们还研究了如何使日志循环以阻止单个日志文件变得超大。
目前还没有具体分析mariadb中的所有日志,关于其具体内容,在随后的章节中会慢慢接触,因为为了找出使用mariadb高级技术过程中产生的问题,那是必须的知识点。
在下一章中,我们将会学习如何使用slow query log 来找到那些执行效率低下的sql语句。并且学习如何优化它们,使其执行更快。 第三章 查询优化
本章将讲解如何提升查询性能。首先我们会引入一个查找慢查询的工具。一旦我们找到了这样的查询语句,就需要找出慢的原因。随后会介绍mariadb中的索引,执行计划(展示了查询语句执行的过程)以及主要的几种执行策略。
本章涵盖一下几点:
1 慢查询日志
2 percona toolkit的pt-query-digest工具
3 索引
4 表统计信息
5 执行计划
6 mariadb优化要点
慢查询日志slow query log
慢查询日志主要是存储那些执行时间特别长的sql语句。使用 slow_query_log 或者 --slow-query-log变量,设置为1即开启。设置为0关闭。在启动参数中不加任何参数默认代表1,即开启慢查询日志。同时 slow_query_log变量可动态调整,服务器运行时可随时开启或关闭。
默认慢查询日志的名字为服务器主机名跟随-slow.log后缀。
和general query log日志一样,慢查询日志可以是文件,表。通过服务器变量指定。可选参数:FILE,TABLE,NONE(会关闭慢查询日志和general query log)。可以设置多个变量,用逗号隔开,NONE优先级最高,如有NONE则其余无效。
比如主机名是 HAL,那么HAL-slow.log就是它的慢查询日志名。你也可以通过slow_query_log_file变量或者--slow-query-file选项起个别的名字。在复制环境下推荐所有日志的名字一样,因为这样可以在所有服务器上执行一套脚本(我觉得作者这里的意思是所有同种类型的日志的名字在各个节点上保持一直。但种类不同的日志还是要通过名字区分开)
如果慢查询日志写入表,那么表名就是mysql.slow_log 第二章里面我们介绍了general_log表。slow_log表的原则和general_log表一样。如之前所说,慢查询日志是用来记录那些执行太慢的语句。但是何为“慢”?这取决于服务器负载。正因如此,语句是否写入慢查询日志是通过服务器一些变量来决定的。
首先不走索引的查询肯定要被记下,设置 log_queries_not_using_indexes(--log-queries-not-using-indexes启动参数)变量为1即可。此变量为动态,但是作用范围为全局。
如果一个查询走了索引,但是执行超时了,也有必要记录一下,通过设置 long_query_time(--long_query_time启动参数)变量为非负数即可,单位为秒。0代表不设超时时间。允许以微秒(最多六位十进制数)为精度的十进制值。但是如果使用slow_log表,则小数位忽略。线程一旦获得了必要的锁,则开始计时。也就是说,在等待其他线程释放锁资源的这段时间不进入计算范围。此变量为动态,全局级,会话级都可。故可以早执行更复杂查询时设置更大的超时时间。
有些查询本身存在一些潜在问题,关于这种情况,可以使用列表用来匹配各种情况,那就是log_slow_filter变量。参数很多,可以设置多个,用逗号隔开,现在我们来看具体的变量值。
full_scan: 查询走了全表扫描(等效log_queries_not_using_indexes)
full_join: 全连接,使用join没走索引的情况。
filesort: 排序操作在内部临时的内存表中完成。
filesort_on_disk:排序操作在临时的磁盘文件上完成。
tmp_table: 查询创建了临时表
tmp_table_on_disk:查询创建了临时表,但是临时表在磁盘上。
query_cache_miss:命中失败,即需要的数据没存在query cache中。
默认log_slow_filter包含以上所有变量,其动态,可配置于会话级和全局级。
即便某个查询,既符合了 log_queries_not_using_indexes也满足了long_query_time(也就是既没走索引又超时),min_examined_row_limit变量也能阻止它被写入慢查询日志。单反返回行数小于这个变量设置行数的查询都不会被记录到慢查询日志中。0代表所有查询都会被记录。最大值取决于具体的计算机平台,但肯定是个超大的值。此变量为动态,可配置于会话级和全局级。有什么用呢?比如说某个查询只返回了几行,走索引完全没有任何优势(优化器根据统计信息裁决)。再比如说,有没有这种情况:返回行数很少,但是查询非常耗时?有的,比如查询非常复杂,包括排序,分组或者还调用了比较慢的函数。
在负载重的系统中记录所有不走索引的查询会给服务器带来很大的压力。因此就出现了这个参数 log_throttle_queries_not_using_indexes 系统变量。设置为0代表不做限制,大于0的数用来表示每分钟允许记录到slow log的且未使用索引的SQL语句次数。当达到这个限制的,以后不走索引的查询将不再写入慢查询日志,一分钟后开始记录最新的不走索引的查询(也就是说中间的就给丢掉了),但是没记录的那些查询会统计个总数写进日志。
全局动态变量 log_slow_admin_statements ,默认情况下像带有管理性的命令都是不写慢查询日志的(比如 check table ,analyze table)。如果你设置为1,那么这样的命令在满足条件的情况下也会写入慢查询日志。
在复制环境中,从端不会复制慢查询日志。因为可以在主端找到(而且复制过来根本没啥用)。通常来说,只有在从端直接执行查询,才有可能产生慢查询日志(这个不奇怪,比如我从端服务器性能甩主端几条街,在主的慢查询在我这边算不上慢。从某方面来说可以是衡量服务器性能的参数。尤其在多源复制技术开发之后,从端服务器可以作为数据仓库中心。译者注)。但是可以通过设置log_slow_slave_statement=1来改变这种策略,此变量为动态全局。设置为1之后,如果主端负载很高而从端很轻,为优化起见,慢查询日志会记录到从端而不是主端,想要这样做,除了设置这个参数为1之外,需要在主端关闭慢查询日志但在从端开启。
为了产生更少的慢查询日志内容,可以使用 --log-short-format启动选项(log-short-format配置参数)。此选项同样会影响binary log。
与上面那个相对的参数叫做log_slow_verbosity。见名知意,它会在慢查询日志中添加一些额外的信息以供参阅,有很多参数,每种参数代表一类信息,如果想添加多种信息,参数用逗号隔开配置即可。参数如下:
1 microtime: 使用微秒为单位。
2 query_plan:包含执行计划相关信息
3 innodb: 记录innodb数据
4 profiling: 开启查询分析
5 profiling_use_getrusage:记录getrusage函数的结果
6 explain:输出EXPLAN语句。
如果想使用 profiling和profiling_use_getrusage,那么必须使用XtraDB引擎。此变量默认值为 'query_plan'. 对于‘log_short——format’和‘log_slow_verbosity’参数,它们只能作用于文件类型的慢查询日志,对slow_log表不起作用.
总得来说,下面4个变量用来决定是否写入慢查询日志
log_queries_not_using_indexes
log_throttle_queries_not_using_indexes
long_query_time
log_slow_filter
下面两个用来过滤上面决定写入的条目
min_examined_row_limit
log_slow_admin_staements
下面两个用来决定什么信息会被记录(会因为记录慢查询内容多少产生相应的负载)
log-short-format
log_slow_verbosity
如果表中没有记录,或只有一行,则不会写入慢查询日志。
基于文件的慢查询日志
服务器启动时,类似下面这行会写入慢查询日志 现在就让我们具体分析一下慢查询日志是如何写入的。下面这个例子是在默认参数情况的一个慢查询条目 注释行提供了关于查询开销的一般信息,意义如下:
1 Time:查询语句执行的开始日期和时间。格式和之前我们讨论过的所有其他日志一样
2 User@Host: 语句的执行人
3 Thread_id:连接的ID号,这个值和 information_schema和performance_schema表中的,以及通过connection_id()函数返回的值相同。在同一行中,还能看见Scheme参数指明的数据库。
4 QC_hit:告诉我们此查询是否命中
注意看最后一个注释行,query_time代表了语句执行时间,单位是秒,如果慢查询日志写入的是文件,那么精度可以达到微秒。如果是写入slow_log表,精度只能到秒。Lock_time 代表了该语句等待其他线程释放必要锁的时间。如果是某个语句由于需要等待其他长时间运行的任务释放锁资源而在这个参数上花费了大量时间,通常情况我们不需要优化这个语句(不如去优化那个长时间运行的任务)。Rows_sent 代表了服务器返回给客户端的行数,也就是结果集行数。Rows_examined :代表了此次查询读取到的行数。因为需要返回给客户端需要的内容,通常需要服务器去分组,排序,连接,筛选等操作之后才能返回正确结果。如果没有使用到足够的索引或者根本就没走索引,那么这个值会大的不正常。不走索引会使得查询语句开销非常大。
然后,我们找到要执行的语句以重复该过程,首先注意到 SET timestamp ,有时候是必要的(比如在where子句中使用了 current_timestamp()函数)。USE 语句通常只能在该连接第一次发生慢查询行为的时候被找到。当然,具体问题具体分析,有时候你不可能直接粘出其中的一些语句就能直接执行。比如如果使用了临时表,用户自定义变量,这些都是不确定因素。但总得来说,是收到了慢查询记录。
如果使用了 log-short-format 启动选项。那么慢查询信息就会更少,比如这样: 这些结果是上面那个比较全的一个子集,其意义参照上面例子,不用再提。
如果使用了 log_slow_verbosity 变量,那么慢查询日志中就有更详细的执行计划信息,执行计划信息对优化sql非常有用,但是通常我们都是手工去做执行计划分析,不在慢查询中使用。
SLOW_LOG 表
slow_log表包含的信息和慢查询日志文件的内容非常相似,但并不完全相等。其包含了默认参数下的慢查询日志内容。内容如下:
左侧是表列,右侧为文件参数,无特殊说明左右侧等效
1 start_time Time
2 user_host User@Host
3 query_time Query_time
4 lock_time Lock_time
5 rows_sent Rows_sent
6 rows_examined Rows_examined
7 db Schema
8 last_insert_id和insert_id,包含关于AUTO_INCREMENT列的信息
9 server_id 列包含了复制环境下的server_id
10 sql_text 列包含了原始的查询文本
11 thread_id Thread_id
来自 Percona Toolkit的 pt-query-digest
pt-query-digest是Percona套件中的一个命令。这个命令可以从慢查询日志,general_log,binary log和show processlist命令中获得具体的查询信息。
想使用这个工具,需要安装percona套件,一般linux发行版中都会包含这个套件。当然你可以从 percona网站上下载 .deb或者.rpm包。mysql和mariadb都可以用。但是仅能在linux环境下,并且需要安装Perl环境。
一般来说,我们更倾向用这个命令来分析慢查询日志,因为慢查询一般是严重问题所在。下面这个例子我们马上就演示,虽然到现在我们还没讨论过慢查询日志,但是没关系。用户还可以使用这个命令来分析general_log。这里不会涉及pt-query_digest的每个参数,我们将通过例子来说明大致如何使用,比如: 假定慢查询日志存在,并且名字为 slow_query。那么pt-query-digest的输出大致如下: 通常我们关注的详细信息是 查询执行时间,锁时间,扫描过的行和发送的行。对于每一项数据,都需要分析其最大值,最小值以及平均值——标准差将告诉我们平均值有多大。和95%列相比,total列也非常重要。在本例中,5%的查询花费了大量的时间。我们可以得知大概这些查询应该是需要被优化的。在其他情况下,通过比较也可以知道很多查询都是欠优化的。因为具有低标准偏差的平均值是才是理想结果。
95%的比率不是瞎说的; 尽管在示例中我们使用了默认值,但可以使用--limit选项进行更改。一般就使用默认值,然后根据需要不断地调整,以找出占用大量资源的查询。(不明所以)
汇总数据如下: pt-query-digest命令会把问题最大的语句排在第一位。事实上,上面这个例子中。第一个查询相当引人注目,它的相应时间非常大,而且还被调用了多次。
几乎不相关的语句会放在最后一行(跟问题最大的那个比较).这样的数据叫做异常值(outliers):这些值不会用于计算平均值,因为它对于整个数据集合不具有代表性。使用 --outliers 选项可以用来界定正常数据和异常数据。界定条件默认是查询时间。但是也可以改成其他条件,比如可以指定一个语句被排除在异常值外的次数。
//* 关于--limit或者--outliers参数具体说明以及其他选项,并不在本书范围之内,详细信息可以去看percona的官方网站 *//
然后我们针对上面那个问题最严重的语句来一个单独的报告,看看具体内容: (没有说明,直接本节结束了!)
索引
在讨论mariadb如何使用执行计划之前,不得不先讨论索引。
索引可以创建在一列或者多列上,具有顺序相关性。如果是对字符串的列加索引,可以根据字符串前缀来定义(具体数据最左边的部分,比如对姓张的,姓王的,姓李的)。对于TEXT和BLOB列,索引强制存在。
//* 极少情况下仅使用一个前缀索引就能加速整个查询,然而,基于节省磁盘空间考虑(索引也会占用磁盘空间)。如果在where子句中只匹配相关列的最左边部分,我们就可以选择使用局部索引。比如姓王姓李这种列,每一个字符或者每组字符都有其特殊意义,使用局部索引可以在节省磁盘空间的同时提升查询速度。*//
本书涉及两种索引:BTREE和HASH.至于像用于几何运算的RTREE,用于全文查找的FULLTEXT全文索引都不在本书范围之内。
至于HASH索引,它只能用于 =和<=> 操作符。不能用于排序和分组,这是由索引类型决定的,因此本书所有例子都是针对BTREE来讲解,HASH索引不会再提。
BTREE的应用场景非常丰富,可以使用许多操作符,比如<,<=,=>,>,like,between,IN.还可以用于排序和分组。
因此,任何语句都可以从建立了B树索引的列中获得速度提升。HASH就只能用在精确匹配索引条目当中了。对于这种查询,HASH索引比B树更快。但是记住,根据不同的查询类型,有些索引可能会被忽略。如果只能走B树索引,也无所谓了,通常这不算性能问题。
B树索引是很多存储引擎默认的索引类型,然而MEMORY是个例外,它用来准确匹配数据,因此默认使用HASH索引。如果HASH不适合我们的查询需求,那么需要专门配置一个B树索引。在随后的 存储引擎和索引一节中。InnoDB可以选择使用自适应算法来静默地将BTREE索引转换为HASH索引。当然,如果后者更适用于当前服务器负载的话。
像之前提到的最左侧的局部索引在mariadb中是可以使用的,比如某个叫col1的列,如下查询会在这种索引中获得速度提升 当然,如果我匹配的事字符串的最右侧,中间部分,或者仅是字符串的一部分,那么这种情况将不会使用该索引,比如这样: 同理,如果一个索引关联了多个列,那么它仅能为最左侧的查询提供速度提升。但不能加速对最左侧的最左部分的模糊查询。比如这样,索引建立在 col1 和col2两列上:
这样可以使用索引 这样就不行了。 即便子句中的列顺序不符合索引顺序,对于order by,group by 这样的子句都可以使用到索引,但是,这样会产生二次排序,数据需要先拷贝到临时表或者文件中,然而再排序。这种I/O量简直可以说是性能杀手,应该极力避免。比如下面这个查询不会产生二次排序: 但是下面这个就会有二次排序了 如果一个查询不需要访问数据表而直接通过索引返回值,那么它的性能提升非常可观,这种情况主要因为 select语句只返回索引列,并且其他所有子句都可以从此索引中获益。那么这种情况叫做 覆盖索引。(说的比较模糊,还是建议去百度看大神贴 https://my.oschina.net/BearCatYN/blog/476748)
表统计信息
即便查询可以使用索引,优化器依然会决定是否使用索引或全表扫描。一般原则就是比较走索引会不会减少读表操作。具体取决于索引的基数,如果是唯一列(文中所说是行中不同的值的数量和行号一样),那么使用索引带来的提升非常大,但是如果行中数据相同的很多,比如说性别啊,地区啊或者一些布尔值。使用索引会非常慢。“可选择性”是衡量where子句中使用索引能否带来性能提升的重要指标。
是否使用索引,优化器会基于几个因素考虑,比如上面提到的索引基数,索引长度,和表数据量。比如如果表数据量很少,那么走索引查询还不如全表扫描来的快。有个问题就是mariadb只能知道基数的估算值,没法知道准确值。但是也基本很准确了,只是有之后和真实数据不大同步。通过基数就能使优化器决定到底是走索引还是走全表扫描。当然你也可以手动重新统计表的索引基数——ANALYZE TABLE命令。其估算索引基数可以通过SHOW INDEX语句显示出来.索引统计信息可以通过存储引擎或者服务器进行收集。比如说除了唯一索引之外,肯定有某列或某几列里面的几行记录是相等的,如果是有索引前缀的话,那就更明显了。(即便在唯一索引中,自增列前缀比如这样的 200000,200001,200002,前缀20000%算做相同)。那么这样出现的相同值的,归为一组,(如果通过索引定位到多个行,那么只能对这些行进行表读了。)。其中有三个参数,主要是对null值做的调整,分别是 innodb_stats_method,aria_stats_method和myisam_stats_method。它们可选参数值的意义相同。分别解释一下:nulls_equal:所有null值视为一组。null很少的话比较好 ;nulls_unequel:所有null视为不同的组,适合于null值比较多,缺点是,比如我其他相同值的组特别多,某组有10000个相同值,另一组有6000个相同值,但是这时候null值也非常多。这就导致出现了很多只有1个相同值的很多组(都是null,但是是不同组)。直接把平均数拉低。可能出现滥用索引;nulls_ignored:直接忽略null值
innodb和myisam默认为 nulls_equal,aria默认为 nulls_unequal.
innodb有一个叫做 @@innodb_stats_on_metadata的变量。默认是关闭的,设置为1后。在每次用户执行SHOW KEYS或者访问information_schema数据库中的一些表的时候会自动收集统计信息。如果对应的表很大,将会花费很长时间 。 从 mariadb10以来,服务器本身也可以收集统计信息。这些数据不仅包括索引列,甚至非索引列也可以收集。主要问题就是这个特性在每次统计一张表的数据的时候都会产生全表扫描。存储引擎收集方式默认是关闭的(Engine-independent statistics are disabled by default),也可以通过服务器变量 @@user_stat_tables来调整。never代表优化器不使用统计数据。complementary表示存储引擎提供不了的统计数据由服务器来收集。Preferably表示尽可能服务器来收集统计信息。实在收集不了的用存储引擎收集(其实这个参数就是权衡两种方式,做一个优先级排序)
存储引擎和索引
对于innodb表,它总是有个聚簇索引。该索引的每一个叶子节点都唯一表示表中的每一行(原文说的实在模糊,我解释一下:就是索引的叶子节点其实就是表中的一行数据,这样的索引只能建立在唯一列上,如果没有这种列,使用自增列,对于oracle来说,参照索引组织表,一个意思。)除了这个索引,还有一种叫做二级索引的,它的叶子节点包含了索引列的值以及对应主键(或者唯一列)的值。只有聚簇索引才有指向表中具体行位置的指针。这种结构比myisam的复杂得多。对于myisam,它的索引就像常规索引一样,包含列的值和具体的位置。对innodb,通过主键列搜索比通过其他列搜索更快(通过二级索引查找会二次访问聚簇索引)
innodb使用主键来建立聚簇索引,如果没有主键,那么使用唯一索引,如果连唯一索引都没有,那么就使用6bytes的不可见列。这样的聚簇索引意味着会比通过主键列建立的聚簇索引有更多的锁冲突。所以对于innodb来说,最好都有主键列。
auto_increment值有一种锁机制来保证多个并发连接访问同一值。锁的工作机制取决于innodb_autoinc_lock_mode系统变量。其参数如下:
0:只在老版本的innodb中可用,代表如果有insert行为,则锁表,直到事物结束。
1:在批量insert和LOAD DATA INFILE时,依旧会锁表,如果是单行insert,则会有一个轻量锁(行锁),基于语句的复制环境中更安全
2:此值不锁表,如果是基于语句的复制,则不安全,如果是基于行的复制,没问题,在 Galera集群中是强制的。
如果innodb_adaptive_hash_index服务器变量设置为1(默认值),那么innodb可以自动把BTREE索引转换成HASH索引,反过来也行。有什么用呢?如果此时数据正好在buffer pool中,而where子句中使用的“=”操作符。那么hash索引的速度比B数索引更快。(自适应)。为了决定使用何种索引,innodb会收集关于表被如何使用的统计信息。增加 innodb_adaptive_max_sleep_delay系统变量可以减少这种收集操作,对负载繁重的系统很有用。
对于aria和myisam,索引存储他们选择的每一列的最小值最大值。因此,select语句不需要任何操作选项,就可以返回最大最小值。
索引的限制(包括每张表可以建多少索引,一个索引可以包含多少列,索引高度,前缀长度等等)具体取决于存储引擎,但通常来说足够用。
有些存储引擎,比如CSV,根本不支持索引。
EXPLAN 漫谈
EXPLAN工具是用来理解mariadb服务器内部如何执行一条SQL语句的工具。在mariadb10版本中,不光可以用来查看select语句,对于update和delete同样适用。语法如下: EXTENDED选项可增加一列输出,并且生成一个NOTE(用 show warnings命令可查),包含了被优化器重写过的sql语句。
mariadb10中,还有一个命令可以使用: 这个命令可以用来查看运行中的sql语句的执行计划,比如有个任务跑了很长时间,我们想知道为啥,那么可以使用这个命令来看看运行中的任务的执行计划是什么样的。thread_id指的是运行中的线程id,通过show processlist可以获取。
下面展示如何使用EXPLAIN语句,以及查看优化器如何对SQL进行重写的(EXTENDED选项) 如果任务执行完毕之后才通过 show explain for thread_id来查看执行计划,那么就会收到一个错误,如下: EXPLAIN适用于select,update和delete,对insert也能用,但是没啥有用信息(我试了一下,大概是这样的) 理解EXPLAIN输出内容
本节我们将会分析一下EXPLAIN的输出内容,同时还会讨论语句是如何执行的以及mariadb中的最重要的优化策略。
对于 JOIN和UNION查询,EXPLAIN会把每个小的SELECT分成两行数据,其可能包含以下内容:
id:每行的唯一标识
select_type:SELECT语句的类型
table:SELECT语句读到的表
partitions:如果是分区表会有具体的分区信息
type:连接(JOIN)的类型
possible_keys:显示查询可以使用哪些索引,这个列表在优化过程早期创建,可能列出的索引对后续优化没什么用
key:mysql决定采用哪个索引来访问表,如果为NULL,则为全表扫描
key_len:mysql使用的索引长度(扫描过的索引大小)
ref:显示了连接两张表的那个列(只有当使用非唯一索引或者唯一索引的非唯一前缀才有)
rows:估算扫描过的行数
filtered:显示由于where条件过滤掉的返回结果的百分比(仅在 explain extended时显示)
extra:额外信息
(关于id列,原文是每行的标识,但是试验发现,它是代表了单条语句的标识,比如我select使用了join,那么计划会分成两行,但是id是相同的,表明是同一个查询,比如) 简单的SELECT语句样例
现在先使用select开始练习一下,我们先创建一张表: 创建表之后,我们执行如下命令: type的值为index,代表了查询使用了索引。具体索引通过key列展示出来为 idx_birth_sex. possible_keys列为空。查询的列全部都有索引,因此使用了索引覆盖,通过Extra就能看出来。
idx_birth索引没有被使用,因为在group by子句中这个索引没有包含所有列。但是idx_birth包含的列idx_birth_sex索引中都有。因此可以使用索引覆盖。记住,索引会影响写入性能,基于优化策略考虑,建立索引要适度。
内部临时表或者文件
有时mariadb为执行语句需要创建一个临时表。如果读取数数据需要进行不止一次读操作,那么把数据复制到临时表,在复制结束时会释放表锁。然而,如果复制的数据量太大,那么会造成巨大的开销。应尽量避免。
内部临时表会用在以下几种情况:
1 对于聚合数据的视图,或使用TEMPTABLE算法定义的视图
2 union操作
3 同时使用了order by 和group by 并且顺序不同
4 在JOIN查询中,不是第一个读到的表的字段使用了order by或者 group by
5 distinct 和order by 同时出现
6 当子查询或者派生表需要物化的时候
如果使用了临时表,则在explain输出的extra列会显示 using filesort或者 using temporary
默认,临时表使用aria存储引擎,以提升group by 和distinct操作的速度。如果设置参数 aria_used_for_temp_tables=0则临时表使用myisam引擎。这么设置是基于aria引擎可能有BUG或者aria不能满足我们的查询要求考量,但是基本不大可能。
通常临时表活跃在内存中,如果临时表大小超过了 tmp_table_size或者max_heap_table_size服务器变量指定的大小,则临时表会写入磁盘。以下几种情况总会导致临时表存储到磁盘中。
1 读取列涉及TEXT或BLOB类型
2 当group by或者distinct子句包含的列超过了512字节(非字符)。此规则同样适用于union distinct
3 union all 操作
UNION 查询:
UNION查询和简单的select语句很不一样。union中的select语句会单独进行优化。现在来看一下执行计划中的union是怎么样的 输出如上,第一行primary,代表了第一个select语句。随后第二行是UNION,此类型用于所有后续查询。最后的UNION RESULT代表了真正的UNION操作
简单索引访问方式
mariadb有几种索引访问方式,其中之一就是range,其在索引中定位了精确的区间。比如下面两个例子使用了范围索引: type列告诉我们使用了范围索引。HASH索引也可以用在范围扫描中,但是由于只支持=和IN操作符,所以你得把具体的值写成列表比如IN (‘1’,‘2’,‘3’,‘4’,‘5’)。
另一种索引访问方法是 index_merge,出现在范围扫描但是使用了不止一个索引的时候,这时候type列就会出现index_merge。
JOIN子句的索引优化
当通过JOIN进行多表连接的时候,表的连接顺序是很重要的。例如,如果一张表有10000行,另一张只有1000行,那么先读小表是比较好的方案。如果where子句还能排除一些行,那么到更大表中进行匹配的行就会更少。这个表的连接顺序和出现在执行计划中的顺序是完全一样的。
基于存储引擎搜索的统计数据可以帮助优化器选择最优的顺序,mariadb存储了索引列和非索引列的数据分布直方图。因此,有时候优化器了解具体数据的可选择性,比如qty=1可能比qty=100更具有选择性。
也可以强制mariadb通过指定顺序去读表,通过使用STRAIGHT_JOIN即可。
优化子查询
依据相关理论,在where条件中包含一个子查询的select语句叫做半连接。通常这种查询性能不如JOIN好,JOIN仅从表中提取需要的列。例如如果我们想知道当前分类中产品的平均价格,那么下面这种语法就显得很直观: 通过JOIN也可以返回同样的结果 mariadb有一种优化策略叫做 table pullout,会把半连接转换成完整JOIN。如果优化器不这么做,查询速度会很慢。table pullout在mariadb5.3之前并不支持。在当前的mariadb版本中,如果子查询很慢。我们应该查看一下优化器是否进行了table pullout。如果没有,那么我们就应该自己完成。
另一个重要的优化策略叫做FirstMatch。用在IN子查询中。一旦记录找到,那么子查询就可能停止(即便子查询中有多个结果都符合也只取第一个匹配到的值)。如果使用了这种优化策略,那么在EXPLAIN中的extra列就可以找到 FirstMatch(tableNumber)字样
LooseScan 是专门用在IN子查询中的策略。比如这样 子查询从satellite表中返回所有的country_code。其中很多国家代码可能是相同的。那么优化器就会把这样的值分组并和左侧表进行连接。避免出现重复结果。如果使用了这种策略,EXPLAIN的extra字段中会显示 LooseScan字样
在老版本的mariadb和mysql中,会对所有派生表(from 中使用的子查询)进行物化。速度很慢,尤其是物化数据量很大的时候。Maraidb不论何时都应该尽量避免。在这种情况下,将转换查询以读取相同的数据,而不使用任何派生表。如果优化无法完成,那么EXPLAIN的Extra列会显示 Using temporary。即便在这种情况下,在物化表上添加一个键还是可以起一些优化作用的。
总结:
本章中,涉及了发现慢查询的步骤以及优化方法。
首先我们分析了如何配置能够筛出需要调优的慢查询。和用来定义何为慢查询的标准。还讨论了pt-query-digest工具。
我们讨论了mariadb的索引。能在一次查询中使用到必须的索引,对于优化来说非常重要。随后我们还讨论了mariadb10中非常重要的特性——存储引擎级的统计数据。它会收集索引和非索引列。这可以帮助优化器选择一个合适的执行计划。
然后。我们说到了EXPLAIN工具。这个工具展示了mariadb执行sql的策略和具体过程根据EXPLAIN的结果和执行计划相关执行,我们能调整索引和查询来达到优化sql的目的。
当然,在并发环境中,仅从一个角度优化查询远远不够,下一张我们将会讨论事物会话见的冲突,锁以及由于隔离级别导致的负载问题。
(注: 关于此处提到的 统计信息不止存储引擎可以收集,服务器也可以收集。此处的服务器应该指的是除去引擎之外的实例了。我并未找到相关资料。并且也为找到文中所说的 @@user_stat_tables. 但是我找到了 use_stat_tables;
所以此处内容请各位读者自行斟酌。
关于作者上面自己提到的,好像是只有服务器收集统计数据的方式才能收集非索引列,不知道下面又怎么变成存储引擎方式收集的可以收集非索引列了。“这种数据”,“这种方式收集的数据”怎么翻译都觉得是在说服务器收集数据方式。
后来我在mariadb的官文中找到了解释。地址如下:
https://mariadb.com/kb/en/mariadb/engine-independent-table-statistics/
官文中并未明确提及 “server collect”这种话,因此是否服务器可以收集统计信息并未明确定论。只是说在mysql库中增加了 table_stats,column_stats,index_stats 这三张表。你可以通过修改其中的数据来改变具体的统计信息。优化器也能根据 use_stat_tables的参数来决定从哪里读取统计信息。 即使用 Engine-independent表,it can control of the statistics)。 第四章 事物和锁 (都能看懂吧,看得懂我就不译了)
因此,事物是保证数据完整的重要特性。保证了当操作失败的时候,数据不会处于不一致的状态。比如管理一个电子商务网站,其中一张存放订单,其他存放可用产品和数量。当一名顾客买了一个产品的时候,那么订单表必然加一,对应的产品数量必须减少。这些操作必须在同一事物中完成。只有这样才能保证服务器在订单记录写入之后,产品数量减少之前崩溃后,整个数据不会生效。如果所有的操作都成功了,这才可以说是一个事物被完整地执行了。
但是,事物还有另一个重要特性——隔离级别。隔离级别有很多种,每种隔离级别代表了不同的隔离类型。无论如何,其中心思想是一个事物型操作不会干扰到另一个事物。让我们回到电子商务网站那个例子。当顾客买下一个产品的时候,应用程序会校验客户购买的产品是否可以售出,随后会减少对应商品的数量。这样的操作会设计在一个事物中,以避免发生类似这样的问题:如果两名顾客同时买同样的产品,其中一名顾客开始了一个事物,并且锁住了她想要买的那个产品的相关的表的行记录。第二名顾客只能等到第一个事物结束后才能下单。只有这样,在第一个顾客买了最后一个库存产品后,第二个顾客才不会买到“虚幻”产品。mariadb中,事物是由存储引擎来控制的,比如innodb,tokudb和spider(mysql也是)
存储引擎用来保证数据一致性的基本机制就是锁,我们将会在本章涉及。同时也会讨论关于ALTER TABLE这个能把大表一锁就锁好久的命令。
INNODB 锁
锁是一种数据结构,由用户获取并关联到相关资源。只要用户持有了锁,那么别的用户就不能修改锁持有者对应的资源,取决于具体的锁类型,可能读,可能读都不行。一般来说,并发操作会进行队列处理。InnoDB可以锁住行和整个表来防止出现在并发操作下可能出现的数据混乱问题。
为了理解Innodb锁是如何工作的,有必要先去学习一下并发事物是怎么回事。这也会帮助我们诊断和处理问题,比如死锁现象。
当事物需要访问一行的时候该行就会被锁住,锁会被事物一直持有,直到事物发起了commit或者rolls back操作。当然,等待锁释放也是有时间限制的,具体取决于服务器变量 innodb_lock_wait_timeout,单位为秒。默认50,通常可以配置稍微少一点。如果有超时情况,事物会被强行中断并抛出错误,比如: 锁模型
Innodb主要有两种锁模型:共享锁和排他锁。共享锁阻止其他连接进行写操作但是可以读。排他锁甚至连读都禁止。隔离级别决定了其他连接是否可以写那行(正持有锁的行)。共享锁在读之前获取,排它锁在写之前获取。不论事物是想获得共享锁还是排他锁之前,都需要先获得一个意向共享锁或意向排他锁。总而言之呢,一个意向锁代表了一个事物正在等待获取一个共享或者排他锁,并且阻止两个事物对同一行记录持有锁。
共享和排他锁一般表示为 S和X。对应的意向锁表示为 IS和IX
如果一条记录上有一个锁的话,那么对于另一个不相容的锁,则无法同时持有,如果有这样的锁,那么连接就会一直挂起。下面这张表展示了不同锁模型之间的兼容情况。 锁类型
innodb支持行锁和表锁
//* 表级锁会锁住整个表,通过发起LOCK TABLES命令即可,但是innodb中最好不要这么干,并且默认也是关闭的,如果你想这么搞,那么设置服务器变量 innodb_table_locks=ON即可。LOCK TABLES命令设计出来主要是用于非事物表的,比如 Aria和Myisam。因为只能锁住整个表而不能单独锁住行,导致性能很差。需要说明的是 information_schema库中那些InnoDB-之类的表存储了锁的相关信息。但是如果是通过LOCK TABLES命令产生的锁,则不会记录在内。*//
行级锁可以锁住一行或更多行。innodb有以下几种锁:记录锁,区间锁以及next-key锁.
1 所谓记录锁就是针对单行记录而言。如果表上没有任何索引,那么锁就会关联到聚簇索引上(之前章节描述过)
2 next-key 会锁住涉及到的索引记录和之前的所有记录。这可以阻止其他连接插入和修改当前事物正在处理的行。next-key锁用在 REPEATABLE READ(重复读)隔离级别中(此处讲的不全,建议百度一下相关例子。另外记住innodb的特点:索引即表)
3 区间锁会锁住一个记录集。这个“集”可以是一条,多条甚至是空的记录。这种锁不会用于唯一索引列,除非索引由多列组成。
一个特殊的区间锁是插入区间锁。在插入新行之前获取,如果此时另一个连接尝试在相同索引值的位置插入一行,则它会挂起直到当前事物提交或回滚。
区间锁不会用在READ COMMITTED(读提交)隔离级别中。
诊断锁
锁虽然可以保证数据的一致性,但同时也会出现性能问题。每个锁都会导致一个或更多个会话处于等待状态,拖慢应用程序。如果某个会话长时间处于等待状态,那么我们必须找出原因并解决。
SHOW ENGINE INNODB STATUS是查看关于锁信息的命令。虽然输出很长,但是很容易理解,并且还分成了几小块,其中有一部分是 TRANSACTIONS ,内容如下: 对于每个事物,都有一个事物ID。如上显示的,最新的事物活跃了9秒。线程ID是4,这对于排错非常重要,通过SHOW PROCESSLIST命令能看到线程4正在做什么。操作系统的线程ID也被展示了出来,不过是以16进制格式,但是可以通过UNHEX()函数进行转换,并且我们还知道有一行被锁了。
在往上看还有一个事物,活动了3秒,在对一个有锁行插入的时候阻塞了3秒。
如果想看详细地信息,可以通过 information_schema库中的INNODB_LOCKS表来查看。需要记住,这张表只包含事物中出现的阻塞信息,表由以下几列组成 1 LOCK_ID:就是个id而已,区分不同锁
2 LOCK_TRX_ID:等效 SHOW ENGINE INNODB STATUS 中的事物ID
3 LOCK_MODE 可能值:S,X,IS,IX;对于区间锁:S_GAP,X_GAP,IS_GAP,IX_GAP;对于自增锁 AUTO_INC。
4 LOCK_TYPE:锁属于行锁还是表锁
5 LOCK_TABLE:那些表上有锁。具体表名由数据库名和表名组成。
6 LOCK_INDEX:对于记录锁,其展示了锁住的索引名称。
7 LOCK_SPACE:对于记录锁,展示了表空间和锁的关联情况。通过INNODB_SYS_TABLESPACES表可以查看具体表到底属于哪个表空间。
8 LOCK_PAGE: 对于记录锁,代表了页号
9 LOCK_REC:对于记录锁,代表页中的记录号
10 LOCK_DATA:对于记录锁,代表被锁住的记录的聚簇索引值。
INNODB_LOCK_WAITS表展示了哪些事物正在等待获取锁,哪些事物正持有别的事物正需要的锁。其列含义如下:
1 REQUESTING_TRX_ID:等待中的事物ID
2 REQUESTED_LOCK_ID :请求锁的ID
3 BLOCKING_TRX_ID:阻塞的事物ID
4 BLOCKING_LOCK_ID:阻塞了其他需要获取锁的事物的锁的ID(注意是锁的ID)
INNODB_TRX表包含了活动事物的信息
下面这个例子通过把INNODB_LOCKS和INNODB_LOCK_WAITS表连接查询,来查看所有阻塞了其他事物的事物,说白了就是发生阻塞的锁的信息。 需要明确的是 information_schema表中关于INNODB相关的表不会包含通过LOCK TABLES命令产生的锁的信息。因为这种锁是通过mariadb服务器本身发起的,并非存储引擎。
SQL语句产生的锁
update 和delete语句会锁住所有扫描过的索引记录,甚至包括那些不满足where条件的。读锁可以释放掉那些不满足where条件的锁,但是有时候也不行。
//* 当修改数据的语句没有使用任何索引的时候,那么表上的所有行都会被锁住。这种情况在使用select for update的时候也会发生 *//
update和delete语句需要对扫描过的每行都获得一个排他next-key锁。
Insert会在索引中需要插入的位置获得一个插入区间锁。插入不同值的事物不需要互相等待。在插入操作之后,一个排他记录锁会锁在新行上,直到事物结束。如果发生重复错误,通常事务不回滚,并将共享记录锁定在现有记录上。如果使用了 ON DUPLICATE KEY 子句,那么排他next-key锁就会锁定在现有记录上。
insert ....select 语句和单纯的insert语句很像,只是没有插入区间锁。在 read committed 隔离级别中,语句的select部分会执行一致性读,与此同时,共享 next-key锁会锁定读过的行
如果有外键,还需要读一些行来保证复合完整性约束,在读到的每一行,都会有一个共享记录锁。
一致性读
本节我们将会讲解事物的一致性读以及innodb如何来保证一致性。一致性查询取决于具体的隔离级别,START TRANSACTION的WITH CONSISTENT SNAPSHOT选项,LOCK IN SHARE MODE或者SELECT FOR UPDATE语句.不同的一致性读参数对应用程序工作的可靠性和提高并发性上有着很重要的影响
non-repeatable 读
所谓non-repeatable读,打个比方,很有可能在同一个事物中执行两次查询但是得到的结果却不一样,然而这个事物又没修改什么数据。这种情况会发生一是因为事物之间没有得到足够的隔离,二是两次查询中间有其他连接修改了数据。
当然,这样可以提高应用程序的并发性,但是开发人员必须意识到会有这种情况出现,并且决定是否允许这样的问题存在。(一般来说都是尽量避免)
read repeatable 是由一致性读和读锁组成的,Next-key锁 保证在查询后在给定范围内插入新值的保护。这些机制都会在本节的随后部分讲解。
Phantom Row(幻读)
next-key避免了幻读的出现。啥?举个例子,比如一个事物在非索引列发起了一个范围查询,像这样 where column between 10 and 20. ,然后这个查询返回了 10,15,20三个值。此时,另一个连接插入了一个13.如果第一个连接又执行一次这个查询,那么它就能看到 13,这就是幻读。
如果是索引列,innodb 使用了next-key锁。第二个连接可以不用等第一个连接结束就可以插入新值。但是新值对第一个连接是不可见的,这就在很好的保证了事物间的隔离。
再举个栗子
首先打开mysql客户端。建表,带索引,插入 1,3,5 随后开始事物(repeatable read隔离级别),并且检索所有值 >=3的数据。 下面开始展示next-key锁
现在打开另一个mysql客户端连接,自动提交模式下。我们插入新值:4.立即得到服务器的反馈。 现在回到第一个连接上,再查一遍,观察结果,然后提交,再查一遍结果,可以看到两次结果的区别 就如之前说的那样,提交之前,新行对第一个事物是不可见的。因此数据在一个事物中才能保持一致性。但是提交后,新行即可见。
一致性读
一致性读指的是在当前会话中从表中读取一致的数据。当一张表被当前事物第一次访问到的时候就会创建一个快照。快照代表了当前表的当前时间点的映像。其他连接对表做的更改不会影响到快照,即便是commit操作。如果当前事物在这张表上做了DML操作,比如insert一行。那么也仅仅是修改了自己的快照。其他连接也不会意识到这种变更。当发出commit命令之后,快照的改变就会映射到真实表上并且对其他连接可见。(注意这里是其他连接可见,不代表当时一定能看见。这和当前连接看不到其他连接提交的改变是一样的道理)。而其他连接对表做的变更当前连接再次查询的时候就也能看见。
//*
请注意,这种技术会导致当前事务查看从未存在的表版本。要使当前连接了解其他连接所做的最新更改,可能需要COMMIT事务并启动新的事务。 *//
一致性读这种情景是在 repeatable-read隔离界别通过 start transaction with cosistent snapshot发起。也可以是在 read committed 的select语句中出现。但是记住,即便是在同一个事物中,每个语句都会使用一个不同的快照。
下面这个例子展示了一致性读的工作情况
首先打开客户端连接,建表,开始一致性读事物随后插入数据: 然后,像之前那样打开另一个mysql客户端连接。自动提交。然后插入一行并确认插入成功。 select 语句显示的只有一行记录。第一个事物插入的记录目前是看不到的。
现在返回第一个mysql客户端中,做提交操作,并检查是否可见 如此一来,提交后,第一个连接插入的记录在第二个连接中就可以看到了,不信通过select看一下: 因为第一个连接提交后,其对表做的所有变更对其他连接都是可见的。
之前的例子中,我们可以随时通过indormation_schema库来查看锁相关的信息。但是一致性读不会产生任何锁,因此下面的例子只是个空行 一致性读原理对delete和update同样适用
对前面的例子来说,如果我们执行语句的时候加上 with consistent snapshot子句。那么第二个连接插入的每一行都会立即被第一个连接看到。
Locking reads(读锁?锁读?)
锁读是另一种保证数据一致性的方法。这种方式比一致性读更强硬。因为它会锁住相关数据,其他连接根本不能访问(那些数据)或者只能读,除非当前事物结束。
锁读通过select中的两个子句发起:LOCK IN SHARE MODE 和 FOR UPDATE。锁的类型取决于使用了哪个子句。
LOCK IN SHARE MODE 会阻止其他连接修改通过SELECT语句返回的行。但是读操作没问题。
对于FOR UPDATE,SELECT语句的行为和UPDATE很像。返回的行被锁定并且不能被其他连接修改和读取,除非使用 READ UNCOMMITTED隔离级别。即使在这种情况下,那些连接将仍旧无法在共享模式下锁定行。
如果当前事物的隔离级别是 SERIALIZABLE并且自动提交关闭,那么LOCK IN SHARE MODE 子句总是加载了select后面,除非使用了 LOCK IN SHARE MODE模式。(the LOCK IN SHARE MODE clause is always added to SELECT statements, unless they use LOCK IN SHARE MODE 你们能翻译吗?)
下面的例子展示了LOCK IN SHARE MODE 如何工作
首先,打开一个mysql连接,建表建索引,索引非常重要,因为innodb的锁就是基于索引来的。然后开始事物并在 SHARE MODE下检索数据 然后打开另一个mysql客户端,尝试查询所有记录然后修改其中两行。其中一行是上面select返回的 a=1的行 如代码展示,select没问题,update a=3 也没问题,但是 update a=1 的时候,由于a=1被第一个连接锁住了,则不得不等待。在本例中,我们保持第一个连接不提交,最后由于超时返回错误。通常,提交或者回滚操作之后,加在行上的锁就会被释放。
死锁
有种组合锁会搞出这么一种情况:事物之间互相阻塞。即持有锁的事物还在等待别的事物释放锁。这种情况叫做死锁。innodb(或者像其他需要处理这种情况的引擎)有一种内部机制来处理死锁。即中断最晚进行插入,删除,更新动作的那个事物。被中断的事物就是牺牲者。
有时候,存储引擎还可能在表级锁上产生死锁。如果是innodb表,可以设置 innodb_table_locks=ON来让innodb检测表级死锁。
并发环境下的死锁是很常见的。当出现死锁的时候,一个或者更多的事物会被中断并抛出1213错误。这不叫事,应用程序捕获了这个错误以后会重新开始事物。当然,也不是说很期望发生死锁。如果经常有这种情况,及时找出问题的根源解决为妙。有几种解决方式:
1小的事物很少会导致死锁。如果允许,尽量把事物写小。语句中使用索引对于减少锁的数量是很有帮助的,之前的部分就说明了这点。不同的事物访问同相同的表,表的访问顺序尽量保持不同,比如如果一个事物访问表的顺序是 A B C,那么另一个事物应该避免使用这种顺序,换一种,比如 C B A会更好。
2 通常和隔离级别无关,但是使用 read committed 或者 read uncommitted 隔离级别可以较少产生因为读锁导致的死锁。
可使用 show engine innodb status 命令来诊断最近的死锁。像下面的例子一样。也可以设置 innodb_print_all_deadlocks=ON,会把innodb的死锁信息写入error log中。
现在举个死锁的例子,搞一个死锁很简单。创建两张表 t1 ,t2。包含一行记录和唯一索引。外加两个不同的mysql客户端。第一个客户端,使用select ... for update 在t1表上获得一个排他行锁。第二个连接,在t2表上搞个同样的锁。然后,使用第一个连接去访问第二个连接锁住的行。第二个连接去访问第一个连接锁住的行。这样,第一个连接会等待第二个连接释放锁,但是锁肯定不会被释放,因为第二个连接还在等待第一个连接释放它需要的锁。这种循环的锁就构成了死锁。例子如下: 没有任何输出,连接1现在被挂起了,因为t2上的b=1正被锁定着。 innodb检测到了错误,然后连接2被中断了。此时连接1会收到查询结果。
我们当然知道发生了什么,但是假如我们想看看死锁的诊断信息。此时就可以使用 show engine innodb语句了。执行它,然后找到 LATESE DETECTED DEADLOCK 部分
】 我们需要的内容相当清晰明了。包括事物的锁类型,对于每个事物,还说明了导致死锁的语句以及哪个事物在等待哪个锁(((2) WAITING FOR THIS LOCK TO BE GRANTED))。最后一行给我们提供了为处理死锁而干掉的那个连接的信息。
事物
用户可以把一句或者多句sql封装成一个事物。这些语句允许我们明确的发起开始,提交以及回滚事物,一般这些都是隐含操作的(比如自动提交,以及有些隐式提交)。我们还能设置具体的隔离级别。明确哪些事物是只读的;这些特性可以帮助innodb做一些内部优化。
事物的生命周期
通常mariadb事物是以 START TRANSACTION 语句开始,以 COMMIT 或者ROLLBACK结束。BEGIN WORK和START TRANSACTION意思一样,但是不能用在存储过程中,因为BEGIN 和END在存储过程中是作为包围代码的关键字出现的。一般开始事物的语法如下: START TRANSACTION AND CHAIN 命令代表在上一个事物的commit或者rollback之后立即开始新事物,即后面不用再输入 START TRANSACTION语句。
有些语句是非事物的,并且对当前事物会有一个隐含的提交操作。这样的语句非常多,并且不同的服务器版本,具体的语句也可能不同。按照一般规则,所有的DML或者DCL和管理命令一样,都是非事物的,DML也仅仅会在需要的时候涉及到临时表。
默认autocommit参数为 ON。可以在全局或会话级修改。如果为ON,那么每条语句都会被认为是一个事物,除非明确使用了 start transaction 语句. 如果关闭自动提交,那么事物就由 start transaction 开始,其实如果你不写这个命令也没关系,因为它是隐含在第一个语句之前和每一个commit或者rollback语句之后。
事物的隔离级别
mariadb 支持4种隔离级别: READ UNCOMMITTED,READ COMMITTED,REPEATABLE READ以及SERIALIZABLE。默认是REPEATABLE READ.可以通过 transaction_isolation变量或者 --transaction-isolation启动参数修改。当然也可以在会话级使用 SET TRANSACTION 命令修改,比如这样 当然,在事物开始以后再更改隔离级别肯定是不行的。
隔离级别决定数据如何被与当前事物隔离的其他事物访问。也就是说,决定了其他事物如何访问被锁住的行以及是否会生成快照。强的隔离级别会在一段时间内阻塞其他连接,因此除非必须否则尽量不使用。例如,如果可以允许一些小错误,那么强的隔离级别尽量就不要用在需要访问大量数据和统计计算的事物中。
read uncommitted 隔离级别
此隔离级别中,当前事物执行的读取操作都会创建一个独立的快照。读操作读取这些快照。这些快照很有可能由一些当前还没提交的事物中的临时状态组成,也就是说,读到的数据有可能从来就没存在过表中。
read committed隔离级别
和read uncommitted 相同,read committed也会对每个读操作创建一个快照,但不同的是,它绝不会用没提交的数据来创建快照,update和delete操作使用读锁,从不使用间隙锁,也就意味着有可能出现插入幻行(不明所以)The UPDATE and DELETE statements, as well as locking
reads, never use gap locks. This means that the insertion of phantom rows is always possible.
repeatable read 隔离级别
此隔离级别中,一个事物中的所有读操作只使用一个快照。这比read committed提供了更好的数据一致性保护。在update和delete操作数据时,记录锁会在唯一索引上生效,并且其他索引的间隙锁或者 next-key 锁会阻止往扫描过的行中插入数据。
serializable 隔离级别
学习serializable可以参照repeatable read。所有的非锁类型的select操作都会自动转换成 lock in share mode.如果我们使用锁类型的 select操作(for update),那么serializable和repeatable read 没什么区别。当使用自动提交时,这两种隔离级别都是一样的。这就是为什么如果当前事物以当前查询结束(就是查完这个事物就完了)则不再需要锁。
实际情况中,serializable可以用在一个事务中的所有查询必须获得至少一个读锁时的情况,比如下面这个例子: 其作用和影响和下面的例子是相同的,只是不用写lock in share mode; 事物访问模式
mariadb 10.0引入了事物访问模式。有两种:READ WRITE 可以修改存在的数据。READ ONLY只能读取数据。例外就是READ ONLY可以修改临时表中的数据,如果尝试访问其他数据则会报错。访问方法可以通过 SET TRANSACTION 命令修改。可以在指定隔离级别的同时指定访问方法,比如: 如果使用了自动提交,mariadb总能知道准确的事物访问方式,如果关闭,或者事物通过start transaction语句开始,那么 read write就是默认的访问模式。
如果存储引擎获悉当前访问模式是read only,那么就可以做出一些优化以提升并发操作性能。
元数据锁
元数据锁是从mariadb5.5中提出的一种特殊类型的锁。当事物第一次访问视图或者表时就会获得元数据锁。非事物表,比如aria 表也不例外,元数据锁阻止事务删除锁定的对象或修改其结构。这是非常重要的,你一定不想在访问表的时候,中途发现表没了或者某个字段消失了。
如果一个连接尝试执行ddl(比如alter table)来修改一个已经持有元数据锁的表,那么这个连接就会挂起直到元数据锁被释放。元数据锁也有超时时间,通过lock_wait_timeout来定义,单位为秒。默认值是 3153600,即一年。如果挂起超时了,那么连接会收到1205错误。
如之前所说,元数据锁在非事物表和视图上同样适用,这使得可以使用事务访问任何类型的实体。如果应用程序使用ddl来操作表和视图,那么 lock_wait_timeout应该被配置的稍微小一些,这样应用程序就会及时收到1205错误。
mariadb 10.0中引入了一个叫做metadata_lock_info的插件。它可以用来查看当前存在的元数据锁。此插件跟随mariadb发布,但默认并未安装。安装之后会在information_schema数据库中创建一个 metadata_lock_info 表,其包含以下几列:
thread_id: 持有元数据锁的线程id
lock_mode: 元数据锁有几种方式,具体取决于被锁住的操作。
lock_duration:这指示元数据锁在事务或语句的持续时间是否有效
lock_type:代表哪种类型的锁(比如表元数据锁还是存储函数元数据锁)
table_schmea:锁住对象的模式是谁
table_name:锁住的表名
在show processlist输出中,处于等待元数据锁释放的连接会在extra列中含有“ waiting for table metadata lock”字样
让我们看一个死锁的例子。使用两个mysql客户端实例。第一个创建一张表,开始一个事物,插入一行。这样就会获得一个元数据锁。我们使用aria表来举例证明元数据锁在非事物表上同样会起作用。随后,第二个连接实例发起 rename table 命令,但是它不得不等待,接着,第一个连接中提交事物,然后第二个连接中的rename才会被执行
第一个连接代码如下: 第二个连接代码如下: 不会有任何输出,此连接正在等待元数据锁释放。
现在我们来查看一下元数据锁信息,方便起见,我们使用第三个客户端实例: 我们可以看到三个锁,第一个连接的ID是4,它对 test.t表持有了一个共享写元数据锁,其通过修改表来呈现其他连接。(???which presents other connections by modifying the table.)。第二个连接的ID是5,它持有了两个意向排他锁,即它在等待获取元数据的排它锁。
现在第一个连接提交事物,在提交前后我们分别对这张表做一个查询: 第一个查询有效,是因为rename table还处在等待状态,但是提交之后,同样的查询失败了,因为表的名字已经被更改了。
需要记住的是,当元数据锁被释放以后,DDl语句会根据队列顺序执行。即便是当前持有元数据锁的事物发起类似alter table这样的命令,因为与它相似的命令已经被队列进去并且需要优先执行。下面的例子展示了这种情景。第二个连接队列了alter table,因此第一个连接执行alter table 就会失败。如果应用程序使用ddl,那么这种情景会导致很多问题并且难以处理。
连接 1 连接 2 客户端展示了aria表上alter table的操作情况,整个过程在第二阶段停止,因为当前连接正在等待元数据锁释放。
连接 1: 不需要复制表的操作有以下几种
1 重命名表
2 更改表注释
3 重命名列
4 更改显示的数据类型大小 比如 int(3) 到 int(4)
5 对于 ENUM列,在ENUM列表的最后添加条目,比如ENUM('a','b') 新加一个‘C’变成 ENUM('a','b','c')
自mariadb 5.5以来,在innodb表上添加删除索引已经不在需要复制整张表了。
mariadb 10.0中,一些额外的子句会添加到alter table后面,其中就有 ALGORITHM。可以用来强制复制表来进行alter table操作(如果受到alter table bug的影响,则可能很有用)或者需要使用一个内置的算法(复制表算一种算法,另一种就是INPLACE内置算法)。这种情景下,如果内置算法不能使用,那么就会生成错误。对于 ALGORITHM可选的值有 COPY,INPLACE或者DEFAULT(使用最佳算法)
ONLINE选项和ALGORITHM=INPLACE等效
可以使用LOCK子句,以便不使用锁,或者只使用共享读锁或排他写锁。如果有更好的锁策略可用,那么LOCK子句不起作用(很有用,如果我们受并发环境下alter table 的bug的影响)。如果要求的锁策略不可用,那么就会报告一个错误。LOCK子句可以使用的值有 NONE,SHARED,EXCLUSIVE和DEFAULT(使用较少限制的可用策略)(这段话我实在翻译不通了。第一句的“以便不使用锁是不是指的下面的 LOCK=NONE??”附上原文:
The LOCK clause can be used so that no locks are used, or only shared (read) locks
or exclusive (write) locks are used. If a better locks strategy is available, it will not
be used (probably useful if we are affected by some concurrency-related bug in
ALTER TABLE). If the requested lock strategy cannot be used, an error will be
issued. The allowed values for LOCK are NONE, SHARED, EXCLUSIVE, and DEFAULT
(which uses the less restrictive available strategy).)
看下面的例子: 对于innodb表,information_schema中的 innodb_metrics会有详细的关于当前执行alter table的状态信息。对于aria表,可以使用mysql 命令行客户端来查看alter table的执行过程。
//* percona 有一个有用的工具:pt-schema-change,包含在percona toolkit中。它创建一张一模一样的空表,在这张表上修改其结构,然后复制数据到这个新表中。最后用新表来替换老的。这个过程比常规的alter table要花费更多的时间,但是其表上不会有锁。mariadb 10.0中,很多情况下这个工具已经不需要再使用了。但是这个工具对于那些修改表结构需要长时间持有锁的老版本的mariadb仍然非常有用,在使用这个工具之前,一定要小心认真读percona的文档。*//
总结:
本章我们学习了并发环境下innodb存储引擎如何保证数据一致性。首先介绍了锁和快照如何工作。从底层学习innodb工作机制以理解innodb事物工作原理是非常重要的。我们还讨论了隔离级别以及每一种隔离级别如何使用锁和快照对数据进行保护。还介绍了在负载繁重的数据库中处理死锁的一般方法,但是身为一名dba应该尽量避免这种问题的产生。我们还讨论了元数据锁,用来保护当前事物操作表或者视图时不会被中途修改结构。
在本章的最后,我们讨论了如何避免alter table产生的会长时间持有的锁。
下一章我们将会学习管理用户和连接,以及如何限制它们的资源和处理一些安全问题 第五章 用户和连接
本章将会介绍mariadb提供的关于安全特性的工具。读者必须有一点用户账户和权限管理的基础知识才行,比如像grant,revoke这种语法,以及权限如何应用到数据库,表和列。
本章主要内容有:
用户账户
角色
SSL连接
认证插件
用户资源管理
线程池
监控连接状态。
用户账户
mariadb的访问控制基于账户。所谓账户,就是连接到服务器的由用户名和主机名拼成的一个字符串(不晓得这么理解对不对,账户这个概念并不等同于用户,而是用户+主机名构成的字符串才叫做账户,但是也没必要去咬文嚼字)。账户的语法定义一般是这样的(单引号可选,如果没有特殊字符的话可以省略):
'username'@'hostname'
建议创建用户的时候使用 create user命令,然后使用grant给其授予相应权限。默认情况下,mariadb允许给一个不存在的账户授权,如果进行这种操作,那么相应的用户就会被自动创建出来。如果说某一天你授权时把用户名输错了,结果系统就给你创建出一个新用户,这样感觉很不爽,所以可以通过 SQL_MODE的NO_AUTO_CREATE_USRE变量来关闭这种机制: 在一个账户中(注意这里是账户,老外把account和user分的挺细),你也可以使用通配符,比如 'user_'@'%' 匹配了所有以user用户名开头的,来自所有主机的连接。当一个账户尝试连接进来的时候,mariadb会在 mysql.user表中查找对应的用户名和主机名(或者ip地址)的匹配项。如果匹配成功,mariadb接着校验密码。密码被加密在system表中,并且客户端在发送密码之前也要对密码做加密处理。如果密码不匹配,或者没有找到匹配的账户条目,则连接会被拒绝
如果密码错误,则能收到这样的反馈信息: 此反馈已经提供了足够我们诊断问题所在的信息,比如像用户不存在或用户不能从某主机访问或密码不正确,都可以。如果括号中显示的password:no,则代表你忘了输入密码。
//* 账户认证加密算法使用的是hash算法,和PASSWORD()函数一样。其派生自SHA算法。许多应用程序都会使用PASSWORD()函数,但仅仅是在内部使用,强烈推荐不要在SQl查询中调用这个函数 *//
有时,多个账户名可以匹配到一个用户名和主机。这种情况下,mariadb就会使用最小匹配原则,比如。'user01'@'mandarino'比'user_'@'%' 匹配程度更精确,也更有选择性,如果有这样的账户连接进来,当其发起命令的时候,mariadb就会按照最小匹配的那个账户所拥有的权限去校验,关联到其他账户的权限都会被忽略。
通过角色授权
直接管理mariadb服务器中多个账户的权限是件很痛苦的事情。假如我们有20个用户需要授予相同的权限,而且将来某个时候还需要更改数据库结构,相应的20个用户的权限也要做相应的调整,最少情况下是20个grant语句。这项工作会极其麻烦而且还容易出错。
正因如此,mariadb10.0引入了遵循 SQL:2003标准的 角色功能。这样,我们可以把一个或一组权限授予一个角色,而不是单个账户,然后可以把这个角色授予某一个或多个账户。这样多个账户就都有了相同的角色。这样,当用户对数据库做出请求时,mariadb会根据其具有的角色来对用户的请求做出反馈。如果数据库方面需要做什么改动,调整权限只需要修改角色的权限即可。
角色可以通过 create role 和drop role 来创建和删除。当创建一个角色的时候,也可以选择这个角色的管理员。参考如下代码: 如果没有使用 with admin ,那么角色的管理者就是当前连接的用户。管理员可以使用grant 和revoke语句来把角色关联到账户上,或者删除这种关联关系。需要注意的是,角色可以关联到另一个角色(可以给角色授予角色)。如果我们有很多角色,这就很有用。比如可以把一些角色需要的公有(也就是说N多账户可能都应该至少拥有的一些权限)的权限都归到一个角色中,而不是给每个角色都去单独地分配权限,另外一些权限可以分给其他的角色。下面这个例子展示了grant,revoke的用法 grant 同样可以用来给角色授权。授予角色权限,再把角色授予用户,则用户就有了其相应的权限。为了撤回授予的权限,可以使用revoke命令。无论如何,语法都是相同的,和给账户授权撤销权限一样。比如这样: 注意,当用户被授予某种角色,即便他们连接到服务器,也不会自动使用该角色。相反,客户端还需要明确使用set role语句来声名角色,并且一次只能具有一种角色身份(如果是角色授予角色,角色又关联到用户的那种,权限本身不会受到影响)。current_role()函数返回了当前活动状态的角色,比如这样: 需要注意的是information_schema数据库中有两张表存储了角色相关的信息。 APPLICABLE_ROLE 包含了当前用户可以使用SET ROLE 设置成哪些角色的信息。例如向谁授予这些角色以及谁可以授予这些角色。 ENABLED_ROLES 表包含了开启的角色名称和授予给其他角色的角色名。 如果一个或多个角色分配给用户已明确选择的角色(通过 SET ROLE明确选择,十分拗口),那么这些角色也会被展示出来,比如: 如上,当前连接用户是josh,我们把writer角色授予josh。这样已经授予给writer角色的reviewer角色会自动的关联到授予了writer角色的用户上。故在enabled_roles表中可以看到两条角色。
通过SSL连接到mariadb
mariadb 支持安全套接字层(SSL)连接。如果想使用SSL,mariadb必须通过编译安装yaSSL或者OpenSSL。二进制安装包已经内置了yaSSL,可以查看have_ssl服务器变量来检查我们的服务器是否支持SSL。如果为YES,则支持,并且已经配置了。如果是DISABLED,则SSL是支持但是还没有配置。如果是NO,则不支持SSL。例如: 为配置SSL,首先需要创建一个证书(由CA发布),为需要使用SSL的服务器和客户端颁发公钥和私钥,证书和KEY可以通过openssl程序来生成(免费软件)。通常UNIX类系统都有安装,windows的话可以另行下载。
//* 本小节内容假定读者对SSL和创建证书过程都有个大致了解 *//
建议密钥串长度使用4096,比2048或者更短的要安全。当然,更长的密钥串代表了网络间会话更高的负载需求。但是测试表明,4096和1024两种长度的密钥串在连接时差距比较大,当进入正常使用时看不出什么区别。不同之处是如果有很多短连接的话,性能负载比较严重。请记住,SSL本身引起的开销通常会占据总查询执行时间的一半或更多。
验证证书和密钥是否到位并且有效的例子如下: 现在我们需要让mariadb知道证书和密钥的位置。让我们把相关信息添加到mysql的[mysqld]一栏中。 当然也可以使用启动选项: 在[client]处需要添加相同的内容: 这样就可以使用SSL来连接到服务器了。
//*最好还是写到配置文件中,省得下次忘了*//
还需要一个使用SSL的账户。如果该账户尝试通过非加密方式连接,则连接会被拒绝,不管账号密码是否正确。使用grant命令即可。 这个例子中,我们会强制u1用户使用SSL加密方式连接到服务器。如果想要对用户的安全性要求更高,还可以在REQUIRE之后跟一些选项,通过AND添加多个。选项如下:
NONE:可以使用SSL,但不是必须
SSL: 此选项说明需要SSL加密,而不对其特性有任何要求
X509:通过x509认证
ISSUER 'str': 此选项说明需要由指定的授权机构发布的有效X509证书
SUBJECT 'str': x509认证需要特定的subject(???我也不是很清楚)
CIPHER 'str':需要使用x509认证,并且连接必须使用一种特定的加密方法
下面的例子展示了上述所说选项: 如上,u1用户从本地连接不需要加密,但是如果从其他地方连接,他必须以SSL方式,使用特定的证书机构(单引号内的一长串)颁发的证书,以及使用RSA-SHA加密算法进行连接。
认证插件
mariadb支持认证插件。这些插件可以以不同的方式来完成登录和退出操作。这用于防止用户通过外部程序来通MariaDB服务器的验证。有些插件需要与客户端适当交互。客户端插件会自动加载并且不需要安装。
目前mariadb 10.0有4种认证插件:
mysql_native_password: mariadb 默认认证插件
mysql_old_password: 老版本,低安全性,在mysql 4.0,甚至是mysql 3.23时使用的插件
unix_socket: 使用Unix用户的证书
pam:使用UNIX类系统的PAM
最后两种默认未安装。可以通过以下命令安装 将来mariadb还会支持更多的插件。(中间省略客套话)。具体的插件支持列表可以通过以下语句进行查询 如果你想要某个账户连接到mariadb时使用特定的认证插件,那么通过使用create user 和grant语句就可以完成,语法如下: 上例中,明确声明了 federico用户使用默认的mariadb认证。随后,我们需要这个用户通过unix_socket插件进行连接。例子如下: USAGE 是一个虚拟的权限,如果我们不想授予任何真正的权限,就可以添加这个关键字,因为不写个权限又会报语法错误。之前例子中,我们使用个USAGE来使 federico获得通过unix_socket来连接到服务器的功能。
为了完成这个例子,让我们看看用户如何连接到mariadb。
以federico用户登录系统shell,并进入mysql命令行客户端: 成功进入!因为我们使用的是federico系统账户,而对于mysql中的同名账户又开启了unix_socket插件。
现在,我们换成root试试: 如你所见,换成root就不行了,但是,你可以使用 --user选项,以特定用户方式连接到服务器: 即在客户端作为适当的用户运行时,便可以连接。
//* 注意,像上面这种--user,只能是系统账户是root才能用,毕竟不能随便是个谁都能以特定用户身份登录到mysql。 *//
mariadb可以通过 grant命令来限制账户的系统资源使用情况。限制如下:
• MAX_QUERIES_PER_HOUR:允许用户每小时执行的查询语句数量;
• MAX_UPDATES_PER_HOUR:允许用户每小时执行的更新语句数量;
• MAX_CONNECTIONS_PER_HOUR:允许用户每小时连接的次数;
• MAX_USER_CONNECTIONS:允许用户同时连接服务器的数量; 上面这些值默认为0,代表不做限制。但是MAX_USER_CONNECTIONS是个例外,如果它为0,那么还有个全局服务器变量 max_user_connections,它默认也是0,代表不做限制。用户可以查询此变量的会话值,以了解此帐户可以建立的最大并发连接数。
限制用户资源的大致语法如下: grant命令有一些语法上的要求,比如说它必须得有一个权限,所以这里使用虚拟的 USAGE权限。随后跟着 WITH关键子,后跟具体的资源限制参数。
下例中展示了如何查看当前账户允许的最大同时连接数,注意,0代表不限制。 //* 如果某个账户达到了它的限制,我们又想临时放开以让它完成工作,可以重新配置带有 HOUR这种这样的参数。但是这种方式不适用于MAX_USER_CONNECTIONS,它不是以每小时为单位的限制量。 值得注意的是,没有办法为单独用户(注意用户和账户的区别)重新配置这些限制。在重新配置完毕之后,需要使用 FLUSH USER_RESOURCES命令刷新资源限制信息,使用FLUSH PRIVILEGES 或者 mysqladmin reload 也可以完成,因为他们都会重新加载整个权限表 *//
一般来说,mariadb和mysql都会在客户端每次连接到服务器的时创建一个线程。这种机制叫做“one thread per connection”,这也是大多数操作系统的默认机制,唯一例外的是windows(从vista开始)
大多数情况下,mariadb都会用来承担OLTP类型的工作。其会经常性地修改数据,比如绝大多数网站和桌面应用。这种工作负载情况通常是一个session连接进来,然后执行了一个短而快的事物,随即关闭连接。如果有许多用户,则意味着在短时间内会有许多session连接到服务器。那么此时“one thread per connection”这种机制就会暴露出劣势,因为创建和销毁线程需要耗费一定的cpu和内存资源,针对这种小而快的连接不停做的创建销毁线程这种动作实在是有点浪费资源。
mariadb5.1中,提出了一个新的线程管理方法——线程池。这种概念的实现是每个线程可以管理多个连接,每个线程都是组中的一部分。mariadb 5.1中,线程数量是固定的。但在mariadb5.5中,对线程池做了重构。此时实现方法是如果当前的线程数量不足以满足新连接要求,则会为新连接创建新的线程,并且当连接不再活跃时销毁线程。但是总会保持一个固定的线程数量。这个数量如果太低,那么并发连接就不会从这种机制中获益,如果这个数量太高,又会白白地浪费资源。
需要说明的是线程池管理方法通过在一组中队列多个线程以避免线程创建和销毁的开销,可以提升OLTP的全局性能,但是对于每个独立的语句和事物可能会慢一些。例如,之前如果执行一个 select version(),那么就会立即返回结果,但是如果使用了线程池,那么服务器还需要做一些更复杂的操作才能进行真正执行这个语句。
线程池方法在windows上稍有不同,毕竟它依赖于本地的线程池管理方法。至于其他操作系统,mariadb都有其自己的线程池管理方式。从用户角度看,windows上的可以使用的配置变量和其他系统有些不同。在windows早先版本中,比如vista之前的版本,并不支持线程池,只能使用原来那种“one thread per connection”的方式。
激活线程池
开启线程池需要在配置文件加入如下内容:
thread_handling=pool-of-threads
对于windows,比vista还老的版本,这个选项会被忽略掉,比vista新的版本,线程池功能又是默认开启的(和别的系统真的很不一样)
如果这行是写在全局配置文件中,并且你不想使用线程池,则可以通过将下面这行添加到自己的配置文件来覆盖上面的参数:
thread_handling=one-thread-per-connection
线程池监控
可以通过thread_handling变量查看是否使用了线程池机制 有两个变量可以用来监控线程的活动状态:
threadpool_threads: 此变量用来查看线程池中的线程的总数
threadpool_idle_threads:此变量代表线程池中空闲的线程的数量———因为线程处于空闲或者正在等待锁释放。这个值在windows中不会被监控
如果使用了“one thread per connection”的方式,则以上两个变量都是0,比如: 配置线程池方法
(说了一大堆就是说注意windows和unix类上面的区别)
线程池的配置变量信息可以通过以下命令查询 类似这样: UNIX系统中配置线程池
以下线程池变量可以配置在UNIX系统中
thread_pool_size: threadpool中group数量,默认为cpu核心数,server启动时自动计算.以下内容摘抄自百度:
thread_pool_size是一个非常重要的参数,控制thread pool的性能,具体表现为thread group的数量。只能在server启动之前设置,我们测试thread pool的结果如下:
• 如果主存储引擎是innodb,thread_pool_size设置在16至36之间,大多数情况设置在24到36,我们还没有发现什么情况需要设置超过36,也只有一些特殊的环境需要设置小于16.
• 使用DBT2或者sysbench做测试的时候,innodb引擎下通常设置为36个,如果在一些写入特别多的环境,这个值可以设置的更小一些。
• 如果主存储引擎是myisam,thread_pool_size需要设置的更低,我们推荐的值是4到8,更高的值可能会对性能有负面影响
原文最后举例,比如如果我们有一台服务器有4cpu,跑着mariadb和web服务,可以只让mariadb使用其中3颗cpu,则设置这个参数为3
thread_pool_stall_limit:timer线程检测间隔,单位为毫秒,默认500, 这个参数对于处理阻塞和长时间执行的语句很重要。这个时间是从一个statement开始执行到执行完成总花费的时间,如果超过设置值就被认定为stalled,此时线程池也开始允许执行另外一个statement。
一般这个值设置为你99%的statement可以执行完的时间。比如我慢查询设置的是0.1,那么这里就设置为10。另外可以通过SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED) FROM information_schema.TP_THREAD_GROUP_STATS;
来获取stalled的比例,这个值尽量的小,为了避免stall,可以调高thread_pool_stall_limit的值。原文:当出现线程停顿(stalled),并且超时,那么线程池将会尝试唤醒线程或者创建一个新的线程。如果线程数量达到了 thread_pool_max_threads指定的值,则不会创建新的线程。
thread_pool_max_threads: threadpool中最大线程数目,所有group中worker线程总数超过该限制后不能继续创建更多线程,默认500,该参数适用于所有操作系统
thread_pool_idle_timeout: 线程最大空闲时间,单位为秒,超过限制后会退出,默认60
thread_pool_oversubscribe: 一个group中线程数过载限制,当一个group中线程数超过限制后,继续创建worker线程会被延迟.原文:此变量为服务器内部使用,用户不应该去更改它,写在这里的原因仅仅是因为会通过 show variables like 'thread_pool%' 显示出来。
windows中配置线程池
注:以下两个变量同样适用于UNIX类系统。
thread_pool_min_threads:线程最少数,当不需要时,windows可以关闭该线程,但是最终数量不得低于该限制。
thread_pool_max_threads:和上面说的一样,不知道为啥这里又提一次。
配置变量调优
现在我们列出使用线程池可能出现的问题以及如何调整这些参数以优化性能。
有时,一个线程为了完成一些操作会锁住几张表。比如,如果发起了 flush tables ... with read lock 命令对非事物表进行物理备份(此内容会在第八章讨论),这时所有的线程(或多个线程)都会被阻塞。通常这会导致线程池继续创建新的线程(比如上面说的超时参数,在不达到最大线程数量的时候就可能通过创建新线程来解决问题),但是这不仅解决不了问题,反而还会浪费资源。因此可以把 thread_pool_max_threads 设置为比较低的值以避免创建类似这样的线程。
有些负载分布很不均匀,有时候会在一段时间暴涨,连接数猛增。但是之后又只剩下为数不多的活动线程。如果说在低峰时段,服务器仍然需要为为数不多的新连接不停的创建线程,销毁线程,显然是很不划算的,因此我们希望mariadb在线程池中尽可能多保留一些线程以供偶尔来的新连接使用。在unix上,我们建议调大 thread_pool_idle_timeout参数,最好可以调整成比两个负载波峰之间的时间段更大的超时时间。至于在windows上,非常简单,通过 thread_pool_min_threads参数保留最少线程数即可。
对于数据仓库,几乎不会从线程池功能上获得什么性能提升,毕竟和OLTP大不一样。产生报表会需要访问大量数据,这样的访问方式倒是不会产生锁(看第4章),但是会长时间阻塞队列中的语句。为避免这种问题,可以把 thread_pool_stall_limit设置的低一些。
搞定阻塞的线程
如果使用了线程池,当一个客户端在所有的表上都获得了锁,并持有很长时间会发生什么?事实上其他线程会被阻塞。为解决此问题。mariadb将会创建更多的线程。但是没用,因为每个新创建的线程依然会等待锁释放。直至达到 thread_pool_max_threads的限制(达到限制也解决不了阻塞状态,只是不会创建新线程了)。此时,服务器将会拒绝所有连接,并且DBA也不能连接到服务器查看到底发生了什么。为了避免这种情况。增大线程池限制也算个办法,但是这会导致线程数量一直都保持很高,其中又有很多是无用的。(我觉得这种弊端作者解释的并不对,增大最大限制不会在所有情况下导致整体变大,这是增大 thread_pool_mix_threads才有的情况,对于unix,除非最小线程数量是通过 thread_pool_max_threads和公式计算得来。问题的关键是如果一直阻塞,最终结果依然是达到最大限制。)
解决办法就是配置一个专有的extra端口,这样的连接会使用“one thread per connection”的方式。这样的连接数量有限制,通常允许1个就足够了。具体变量如下:
extra_port:指定额外连接的端口号
extra_max_connections:允许使用额外端口号进行连接的并发连接数。
//* 这项技术也能允许连接进入以完成工作,不过是以“one thread per connection”的方式。但是 extra_max_connections得足够大才行 *//
监控连接
show processlist命令可以返回当前的活动连接。其中 information_schema数据库中的 processlist表还包含一些额外信息。我们很快讨论它。同样,在 performance_schema数据库的 threads表中也包含了 show processlist命令返回的信息,以及其他列。通常 show processlist会返回简明扼要的信息。processlist表更高级一些,它可以用在存储程序中。
下面的例子展示了 processlist表和 show processlist命令的输出结果: 下面这张列表展示了 processlist表和 show processlist命令输出列的含义与不同之处(我就不翻译了,几乎都是见名知意的字段。) 查询threads表不会产生锁,但是访问performance_schema数据库会导致性能问题,会影响很多服务的正常运行。这就是为什么mariadb10.0中默认是关闭performance_schema数据库的。它不仅存储了客户端的连接信息,还包含了所有线程甚至内部线程信息。threads表包含了show processlist命令返回的所有内容,皆是以大写的 PROCESSLIST_前缀展示的。还包含其他几列:
thread_id:线程id,和processlist_id不一样。
name:线程的类型,比如与客户端相关的连接,该值可能是 thread/sql/one_connection.
type:如果是 BACKGROUND,则为mariadb内部线程;FOREGROUND则是可以通过 show processlist展示出来。
PARENT_THREAD_ID:父线程ID
ROLE:总为NULL,目前没什么意义,可以忽略
INSTRUMENTED: 是否在performance_schema数据库中跟踪线程活动状态.(PO个例子) THREAD_ID 列可以与performance_schema数据库中的其他表关联查询,比如这样: 下面这张表展示了 COMMAND列的含义。(与processlist表和show processlists相同,此处不做翻译,见名知意) PROCESS 的STATE列
STATE列包含线程当前正在做什么,有很多值,但是此处只列出主要的一些(同样不做翻译) 中断连接
当查看process列表的时候,你可能会注意到其中一些进程很慢,或者阻塞了其他进程。也可能你发现它们一直处于沉睡状态。你想停止它们。此时可以使用KILL命令。mariadb对此命令有很多可选参数,对比mysql中的语法,大致如下: 默认KILL会终结一个连接。 CONNECTION关键字只是读起来顺嘴,与不含connection的KILL一样:它会终止给定thread_id有关的连接。如果使用了 QUERY关键字,那么只有连接执行的语句会被杀死,但连接本身还会保持。 连接的ID或者语句的ID必须显式指定。即便是对于QUERY关键字。 仅当指定了ID关键字时,MariaDB才会去查询ID。
另一种做法是可以加USER关键字,后接具体的账户名称或者用户名,这样其用户相关的所有连接或查询都会被杀死。如果是想杀死属于我们自己的连接,也可以使用current_user()函数。
杀死一个查询或者连接,使用SOFT选项虽然慢,但是更安全,这也是默认选项。使用HARD选项速度很快,但是方式很野蛮,但是如果使用SOFT太慢的话,用一次HARD 也无妨,但是在HARD 操作之后,数据有可能损坏,此处展示一下该命令如何工作
1 KILL命令给目标连接打个标记,如果该连接被KILL标记了,则在 show processlist中的INFO列会显示为 'Killed'
2 如果是安全地进行中断当前操作,目标端会校验标记是否已设置,如果是,那么连接或者语句就会被中断。
3 如果是非安全的中断,通过HARD KILL的连接会被立即中断,可能会使表处于不一致的状态。
如果连接涉及了aria或者myisam表,那么一是数据有可能损坏,二是如果表上有索引,那么索引需要重建。
下面展示了KILL的例子:
使用mysql 命令行客户端,执行如下: 此命令无害,但是对于演示例子非常好,因为它只是让当前线程暂停2000秒.
然后,开启另一个客户端执行下面命令: 输出内容很多,但是我们只关心其中几个,比如我们知道了线程ID为 9.然后执行下面的命令: 可以通过 show processlist命令查看KILL的工作情况。
如果是一个非活动状态的连接,在其达到wait_timeout参数设置的超时时间后会被自动关闭。
总结:
mariadb 支持通过SSL进行连接以保证更好的安全性。认证插件也可以使用额外的认证源来替代mariadb自己的认证方式。我们还尝试使用unix_socket来演示了如何使用系统认证。PAM认证也是支持的。还有通过限制用户每小时可以执行的语句,以阻止其消耗太多资源。
有可能用户会通过特定的主机和SSL或者认证插件来进行连接。完全可以实现,因为mariadb的权限验证基于账户,账户是由用户名和主机名组成。其账户还可以使用LIKE 通配符。
mariadb支持两种线程管理方法:“one thread per connection” 这种传统方式,以及线程池这种新方法,线程池会把线程分成组,以提升连接性能(针对OLTP).针对unix和windows,mariadb有两套配置方案,实施的时候需要注意。
show processlist命令,information_schema.process表,performance_schema.threads表可以帮助监控连接的活动状态。如果有必要则可以使用KILL命令杀死你想要终结的连接。
下一章我们会讨论mariadb中的cache和多种存储引擎. 第六章 Cache
(写在本章开始:buffer,cache 都翻译成缓存,或者缓冲,但我们知道其实英文含义并不相同,所以,我尽量)
表和索引通常会占用大量存储空间,比如像磁盘,SSD,或者闪存设备。访问这些设备速度很慢。如果服务器不得不经常这么做的话,那么I/O就会成为性能瓶颈。为避免访问磁盘设备,mariadb和存储引擎提供了几个DBA应该掌握的CACHE。本章将会涉及以下几个主题(并非不做翻译,专业词汇尽量保持原样,没法再翻译了)
innodb buffer pool 和 doublewrite buffer
myisam key cache
aria page cache
query 和 subquery cache
table open cache
main per-session buffer
//* 在讨论具体的cache分配之前,有些注意点需要说明一下。许多权威性著作,比如《mariadb knowledege base》或者mysql 手册都建议给innodb buffer pool设置比较大的内存,或者如果很多表都是非 innodb表,就把默认存储引擎的main cache设置大一些。许多文章中都建议把buffer pool设置为物理内存的70%到80%.对于myisam和aria来说,因为它们只有key需要被存储引擎缓存,不需要很大的内存。至于缓存数据文件,mariadb则依赖于操作系统。除此之外,dba应该认真思考如何为其他cache分配内存。比如query cache,如果用,则肯定小不了。如果有很多并发连接,则per-session cache也会占用相当大一部分内存。而且,这些建议也仅仅是针对一台服务器上只运行一个mariadb实例的情况,其他程序资源分配同样需要考虑进去。 *//
innodb cache
innodb存储引擎应用非常广泛,会配置它的参数非常重要。innodb buffer pool可以加速读写操作。因此DBA需要掌握它的工作原理。 doublewrite buffer 是一种保证一行记录写入文件时绝不会处于半写状态的机制。对于写负载很重的系统,可以关闭这个特性以提高速度。
innodb pages
表,数据和索引组成了page(innodb最小的存储单元)。其存在于cache和文件中。一个page由1或2行数据和一些空余空间组成。已使用的空间和page总大小的比率叫做 fill factor(填充因子,就是页占用率)
如果改变了page的大小,则填充因子也会相应改变。innodb会保持填充因此处于15/16。如果page填充因子低于1/2,innodb会把该page和其他page合并。如果行数据是顺序写入,填充因子会介于 1/2到15/16之间。填充因子过低,表空间浪费严重,如果填充因子过高,当因为update导致行长度增长时,那么就会产生重组,这对性能会有消极影响(类似于oracle中的pct_free,行链表接,行迁移内容)
变长格式列(比如 TEXT,BLOB,VARCHAR 或者VARBIT)会存放在一种叫做overlow PAGE的数据结构中。这样的列叫做off-page列。它们由DYNAMIC行格式管理,DYNAMIC行用在大多数表中,并提供向后兼容。行格式会在随后讲到
page绝不会改变它自己的大小,并且所有page大小都是相同的,但是页大小可以配置,比如 4KB,8KB,16KB.默认是16KB,这个大小是针对多种负载情况选择的最优大小,优化了全表扫描。然而,更小的size可以提升OLTP的性能。因为插入频繁,而每次申请的内存数量会更小。另一个更改page size的因素就是它对innodb表压缩有着很大的影响。(第七章会讨论)
可以通过配置文件中的innodb_page_size 变量更改page size,重启生效。
innodb buffer pool
如果服务器主要使用innodb表(绝大多数都是这样),那么配置buffer pool就是一个非常重要的工作。理想状态下,buffer pool应该能容纳所有的innodb数据和索引,来让mariadb无需访问磁盘就能获取数据。数据变更会在buffer pool中执行,随后再写入磁盘以减少I/O 操作。当然,如果buffer pool不足以容纳所有的数据,那么会把访问频率最高的数据放进去。
buffer pool 默认大小128MB,可以更改,生产环境显然太小,如果是开发环境,又用不了这么大,此参数最小值为5MB,给开发用妥妥没问题。
Old 和 new page
我们可以把buffer pool想象成由data page组成的链表,其链表使用LRU算法。此链表分成两部分:new链表包含最近经常使用的页,old链表包含最近很少使用的页。
每个子链表的第一个页叫做 head (头). old链表的头又叫做midpoint(中点).当一个不存在于buffer pool中的页被访问的时候,它会被插入到midpoint中。old链表中的其他页逐一往后挪一步,最后一个页被丢弃。
当old链表中的页被访问的时候,它就会移动到new链表的head(头部).
//* 原文这里是算法介绍,提供了一个地址: http://cs.gmu.edu/cne/modules/vm/yellow/lru.html *//
当new链表中的一页被访问的时候,该页会移动到new链表的头部。以下几个变量可以影响上述算法。
innodb_old_blocks_pct: 此变量定义保留到old链表缓冲池的百分比。可选范围为5到95,默认是37(3/5)。
innodb_old_blocks_time:如果该值非0(以毫秒为单位),则进入老链表的页必须在达到这个参数设置的时间之后再被访问才会被提升到新链表的头部,如果没有达到该参数设置的时间限制,即便被访问多少次,也不会提升到新链表头部。如果为0,则只要被访问到,立即提升到新链表头部。
innodb_max_dirty_pages_pct:定义了内存中可以保留的最大脏页的百分比。此机制会在本章后面讲解。此参数并非硬性限制,但是innodb会尽量满足该限制。该参数许可范围为 0到100,默认为75.增大该值可以减少写操作的频率,但是如果意外宕机,则再次开机需要花费更长的时间(类似于oracle中的online log 大小,越大实例恢复速度越慢.)
innodb_flush_neighbors:如果为1 ,则当脏页写入磁盘时,临近的页也会被写入。如果为2,同一区块中的脏页会被一起写入。如果是0,则只有超过 innodb_max_dirty_pages_pct的部分或者当它们被buffer pool踢出时才会刷写到磁盘。默认值是1.这种将多个IO合并为1个IO的操作仅对于传统机械硬盘有性能提升。对于写入量很大的负载可能需要更加积极的刷写策略,但是如果页面写得太频繁,则会降低性能。
buffer pool 实例
在mariadb5.5之前,innodb只有一个buffer pool实例。这样并发线程会被互斥机制阻塞而成为性能瓶颈。如果并发非常高,并且buffer pool非常大,这种阻塞现象会尤为明显。因此把大的buffer pool切分成多个实例可以解决此问题。
多个实例仅在缓冲池大小至少为2 GB时才展现出优势。每一个实例都应该设置至少1GB。如果buffer pool的大小少于1GB,则mariadb会忽略该配置并只使用一个实例。此特性对于64位操作系统更有用.
以下两个变量控制buffer pool的实例和大小
innodb_buffer_pool_size:此变量定义buffer pool的总大小(非单独的实例). 注意,实际尺寸将比这个值大约多出10%,多出的10%专门用于change buffer (第七章表压缩提到)
innodb_buffer_pool_instances:定义buffer pool的实例数量。如果为 -1,则innodb自动决定实例数。最大值为64.UNIX上默认为8,windows上要看具体的innodb_buffer_pool_size设置。
Dirty pages 脏页
当一个用户执行一条语句修改了buffer pool中的数据。innodb会先在内存中修改。仅在buffer pool中被修改过的页叫做脏页,未被修改过的页或者已经被写入磁盘的页都叫做干净页 clean page(翻译成中文好别扭)
注意!变更的数据记录会写入到redo log中。如果在脏页写入数据文件之前发生宕机,innodb通常可以恢复这些数据,包括最新修改过的内容。通过读取redo log和 doublewirte buffer即可完成。doublewirte buffer会在后面提及。
把脏页刷写到innodb数据文件的线程,在mariadb10.0中叫做 page cleaner。在更老的版本中是由master 线程完成。其执行几个innodb的维护操作。刷写过程不仅可以刷写buffer pool,连innodb 的redo 和undo log也不会放过。
当事物在物理层写入数据时,脏页会被频繁的更新。其自己的互斥机制保证了并不会锁住整个buffer pool。
脏页的最大数量取决于 innodb_max_dirty_pages_pct的设置,当达到最大限定时,脏页会被刷写。
innodb_flush_neighbor_pages决定了innodb如何选择页来刷写。如果设置为NONE,则只有选中的页会被刷写。如果是area,那么临近的页也会被刷写。如果是cont,所有连续的脏页都会被刷写.
关机过程中,设置innodb_fast_shutdown=0会进行完整的页刷新操作。一般来说应该是这样,因为这样会保证数据处于一致状态。然而如果仍有许多变更还未写入磁盘,那么整个关机过程会非常慢,如果你想加速关机过程,可以把此参数设置为1或者2.但是这样下次启动时就会变慢。(原文并未交代具体参数的作用,以下摘自某贴:
Innodb_fast_shutdown告诉innodb在它关闭的时候该做什么工作。有三个值可以选择:
0表示在innodb关闭的时候,需要purge all, merge insert buffer,flush dirty pages。这是最慢的一种关闭方式,但是restart的时候也是最快的。
1表示在innodb关闭的时候,它不需要purge all,merge insert buffer,只需要flush dirty page。
2表示在innodb关闭的时候,它不需要purge all,merge insert buffer,也不进行flush dirty page,只将log buffer里面的日志flush到log files。因此等下进行恢复的时候它是最耗时的。
(拿oracle来对比,0代表了 shutdown immediately,完整,干净地关闭数据库。2代表实例崩溃过程。再次重启需要进行实例恢复过程)
read ahead优化
read ahead 特性被设计用来减少磁盘的读操作。它一次就读取到那些可能会在未来被读取到的数据。
有两种算法来选择提前读取的页
线性 read ahead
随机 read ahead
线性 read ahead默认开启。它会对buffer pool中顺序读取到的页进行计数,如果数量超过或等于 innodb_read_ahead_threshold的值。则 innodb会从同一个extend中读取所有的数据。innodb_read_ahead_threshold值必须只能被设置为0到64之间的值。设置为0关闭线性read ahead但是也不会开启随机read ahead。默认为56.
随机read ahead仅在innodb_random_read_ahead=ON时开启。默认为OFF。此算法会检查buffer pool中是否有至少13个页被读取到同一extend中。此时不关心是否是顺序读取的。如果此变量开启,那么整个extend都会被读取。13这个值无法另行配置。
如果 innodb_read_ahead_threshold=0,且 innodb_random_read_ahead=OFF。则read ahead优化不再使用。
buffer pool 性能诊断
mariadb提供了一些工具来监控buffer pool和innodb主线程的活动状态。通过监控这些状态,dba可以调整相关参数以提升服务器性能。
本小节中,我们将会讨论 show engine innodb status 语句和 information-schema.innodb_buffer_pool_stats表。前者更易读,后者更详细。
innodb_buffer_pool_stats表包含一下几列(不做翻译): 这些值每次显示都不同。
pages_mad_young_rate,pages_not_made_young_rate分别代表了变成new page和那些在一定时间内没有被访问到的old page的频率(pages count /s)。如果前者值太高,则old 链可能不够大。反之亦然。
read_ahead_rate和read_ahead_evicted_rate是两个用来优化read ahead特性非常重要的参数。read_ahead_evicted_rate值应该比较小,因为它表示那些通过read ahead方式读取到的但是无用的页。如果这个值正常,但是read_ahead_rate很低。那么很可能是read ahead读到的内容很多都被用到了。如果使用的是线性read ahead,那么可以通过调整innodb_read_ahead_threshold参数进行优化。或者使用其他算法。
以RATE后缀结尾的列,可以表述服务器当前活动状态。这些值应该在一天,整周或者整月多次去检查。可以使用一些监控工具比如cacti或者nagios。percona monitoring tools包含了mariadb和mysql的插件为这些工具提供监控接口。
导出和加载buffer pool
有些情况下可能需要保存buffer pool当前的内容以便在以后需要的时候重新加载它们。最有可能的情况就是服务器停机,随后开启。但此时buffer pool是空的,innodb需要填充进有用的数据。这个过程叫做warm-up.直到warm-up完成,innodb的性能都会比平常低。
两个变量可以帮我们避免warm-up:innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup。如果设置为ON。innodb会在停机时自动保存buffer pool的内容到文件,在启动时从文件中加载。这两个变量默认为OFF。
开启它们很有用,但是需要记住:
开机和停机时间可能会更长。如果有特别需求,自行斟酌
存储buffer pool内容需要占用一定磁盘空间。
用户有时候会随时导出buffer pool,然后不重启数据库直接恢复buffer pool的内容。这种操作建议在buffer pool已经处于优化状态,但是随后的操作会对buffer pool中的内容影响很大时进行。比如当对一张大的innodb表进行全表扫描时,逻辑备份时,都会把一些平时不怎么访问的数据填充到old list中。因此比较好的办法就是提前备份出buffer pool,做完相关工作以后再把备份的buffer pool倒回去。
通过Innodb_buffer_pool_dump_status、Innodb_buffer_pool_load_status可查看dump/load的状态.如果加载buffer pool花费了太长时间,可以通过设置 innodb_buffer_pool_load_abort=ON来中断导入过程。dump 文件的路径和文件名可以通过 innodb_buffer_pool_filename进行指定。需要确保有足够的空间来存放dump文件,但其实用不了多大。
innodb change buffer.
change buffer 属于buffer pool中的一部分。其包含了与脏页相关的二级索引内容。如果修改过的数据在后来被读到,将会合并到buffer pool中。在老版本中,这个buffer 叫做insert buffer。后来改名了,因为它可以支持更多操作类型的缓存。
change buffer 可以加速以下几个写操作:
insertions:当新行写入
deletions:当存在的行被标记为意图删除,但基于性能考虑还未真正进行物理删除。
purges:物理删除之前标记的行和索引。 这是由专用线程定期完成的。
有些情况下,我们想关掉change buffer。例如我们有一个工作任务,只有关闭change buffer的时候内存大小才能满足要求。这种情况下,即便关闭它,仍然需要频繁地访问buffer pool的二级索引。如果我们的服务器很少进行DML操作,或者几乎没有二级索引,那么关闭change buffer是个不错的做法。
change buffer 可以通过以下两个变量进行配置:
innodb_change_buffer_max_size:change buffer的最大大小。数值为buffer pool的百分比。许可范围为0-50.默认为25.
innodb_change_buffering:定义何种操作使用change buffer。none(关闭所有buffer),all,insert,deletes,purges以及changes(包含inserts和deletes,不包括purges)。all为默认值。
浅析 doublewrite buffer
当innodb把页写入磁盘时,至少两个事件可以中断写入操作:硬件错误或者系统错误。如果页面不大于系统写入的块则不可能产生系统错误。
此时 innodb redo和undo 不足以恢复半写的页面,因为为性能考虑,它只包含页的id,但不包括数据。
为避免半写页,innodb使用 doublewrite buffer.这种机制对每个页进行两次写入操作。只有第二次写入成功,此页才算有效。当服务器重启,如果进行恢复操作,半写状态的页会被丢弃。doublewrite buffer会对性能影响不大,因为写操作是顺序进行,且是批量写入的。
如果数据并非很重要,也可以关闭doublewrite buffer,通过设置innodb_doublewrite=OFF或者以 --skip-innodb-doublewrite参数启动皆可。
如果性能问题非常重要,并且我们使用的是快速存储设备,根本不想给磁盘带来任何额外负担,哪怕一点点也不想。但是数据的准确性又十分重要,又不想直接关掉doublewrite buffer 机制。mariadb提供了另一个叫做 atomic writes(原子写入)的机制。这使得写入操作就像事物一样:要么完全成功,要么完全失败。半写状态的数据不可能存在。然而mariadb并不会直接支持使用这种机制,需要使用FusionIO之类的存储设备来实现。FusionIO闪存速度非常快。为开启这种原子写入机制,可以设置 innodb_use_atomic_writes=ON,开启此功能会自动关闭doublewrite buffer。
MysISAM key cache
MyISAM索引缓存在一个称为key cache或者key buffer 的数据结构中。此buffer可以极大地减少物理索引文件的访问。缓存的索引会在内存中被修改,会在随后被刷写到磁盘中。
key cache可以关闭。mariadb中没有对于数据的cache结构而是由操作系统本身进行缓存的。 如果禁用key cache,也是同样的。
如果系统主要使用MYisam表,那么key cache会变得非常大。否则,key cache可以设置为一个非常小的值。完全关闭key cache是不可能的。如果内部临时表使用的不是aria表(aria_used_for_temp_tables=OFF),那就是myisam了。
key cache大小由key_buffer_size变量控制。
key cache和myisam 索引文件一样,由块结构构成。一个块是一小段连续的内存空间。索引文件和key cache可以使用不同的块大小。但最好为最小内存块大小的整数倍。索引文件块和key cache块大小可以通过 myisam_block_size和key_cache_block_size变量控制。key cache 块会在内存中被修改。因此,修改过的块叫做脏块,未修改过的叫做干净块。
LRU和中点插入策略
默认情况下,myisam使用LRU算法来判断哪些blocks会被存放到cache中,如果key_cache_division_limit=100则使用此算法。纯LRU算法有一个类似于InnoDB缓冲池解释的问题。如果一个查询做了完整索引扫描,它会访问到那些不经常访问的块。这样的块会被放入到cache中,导致应该保留在cache中的块被换出。
另一种方法是将key cache存分为两个子列表:warm list 和 hot list。这些列表的长度不是变量,而是专用于warm list的key cache的最小百分比,由key_cache_division_limit变量决定,总保持warm list在这个百分比以上。(国内称作 warm sub-chain 但原文仍叫做 warm list )
当一个不在cache中的块被访问到时,它将插入到warm链表的尾端,其他warm块将向链表的末尾移动。 位于warm list开头的块会被首先换出。
如果一个warm list中的块被访问超过三次,它将会移动到hot list 的尾端。当其他块被插入到热链表时,其他块会被移动到链表的尾端。如果hot list 顶部的块在热链表的尾端保留了一段时间,那么它就又会被移到warm list中,保留的时间通过如下公式计算: blocks * key_cache_age_threshold / 100。blocks是cache中块的数量,key_cache_age_threshold是系统变量。调大key_cache_age_threshold可以让块在热链表中保留更长的时间.
以下内容摘自网络,写的比原文更加详细:
(1) 该策略将前面的LRU队列(LRU Chain)分成两部分,hot sub-chain和warm sub-chain。并根据参数key_cache_division_limit划分,总保持warm sub-chain在这个百分比以上。默认情况key_cache_division_limit是100,所以默认时候只有warm sub-chain,即LRU Chain。
(注:Multiple Key cache情况,每个key cache都有对应的key_cache_division_limit值)
(2) 在warm sub-chain中的某个block如果被访问(Access)次数超过某个值时候,就将该block放到hot sub-chain的底部。
(3) 在hot sub-chain中的block会随着每一次的hit调整位置,hit越多,越接近底部。在顶部停留时间过长就会被降级到warm sub-chain中,而且是warm sub-chain的顶部(很可能很快就会被移出key cache)。
(4) Hot sub-chain中的顶部的block停留时间超过一个阈值后就会被降级到warm sub-chain。这个阈值由参数key_cache_age_threshold决定。具体的计算方法是:设N为key cache中的block个数,如果在最近的(N*key_cache_age_threshold/100)次访问中,key cache顶部的block仍然没有被访问到,那么就会被移到warm sub-chain的顶部。
(5) 默认情况key_cache_division_limit = 100,这时只有只有一个Chain,所以不使用该策略。即退化的Midpoint Insertion Strategy是LRU算法。
key cache 实例
在一些操作中,key cache由锁来保护。此处有个问题就是多个连接同时访问时会有冲突,有两种机制可以用来减少争用:使用多个key cache实例和key cache段。每个key cache实例有它自己的锁。因此每个实例都能单独地进行配置。而默认的cache不能被移除或关闭。
默认情况,只有一个默认的key cache实例存在,如果想增加更多的实例,可以通过设置key_buffer_size变量进行创建。语法如下:
SET [<instance_name>.]<variable_name>=<value>;
例如,为了配置三个cache,包括默认的,运行如下命令: 没有办法查看所有的与非默认实例相关的变量(就是说show variables无用)。与实例相关的变量只能被独立地查看。
需要记住,这些设置会在重启时丢失。因此最好把配置信息写入配置文件,比如这样: 注意,配置文件中不能使用2000*6000这种表达式。
如果想看有哪些key cache以及相关信息,可以通过查看 information_schema.key_caches表。比如这样: 如果想关闭一个cache,设置它为0即可: KEY_CACHE表包含以下几列(不做翻译): 如果一项任务的索引条目包含在key cache中,则读取和写入的数量,尤其是等待读取和写入的数量在正常数据库活动期间应该非常小。
大量未使用的块代表了实例或段已经产生了碎片。单独对一个实例或段进行碎片整理是不允许的。
每个索引只能存放到一个key cache实例中。同一张表上的索引只能放到同一个cache中。如果与表关联的cache没有特别指定,则使用默认cache。因此如果只有一个cache实例,那么无需为某张表特殊指定其他cache。如果想把不同的cache与表进行关联,需要使用cache index语句。这种建立的关联关系是可变更的:可以稍后将表重新分配给不同的cache。
基本示例: 上例中,myisam1和myisam2表被缓存到hot_key_cache实例中。
如果是分区表,那么不同的分区可以关联进不同实例的cache中: ALL关键字代表了所有的分区必须被关联到特定的cache中。此时partition子句可以被省略。但是对应的key cache必须存在,否则报错。
和实例配置一样,服务器停机则表和cache实例的关联关系就会解除。因此,cache index语句最好写入到init文件中。init文件可以通过配置文件中的 init-file选项进行配置,它在服务器启动时执行。
key cache 段
另一种减少冲突的方法是使用key cache 段。每一个key cache是由几个段组成的。为使用此特性,可以通过key_cache_segments变量进行配置。其代表了段的数量,最大为64,最小为0,代表关闭此特性。
正如之前所说。如果使用了key cache段。则information_schema.key_cache表中有些行是以不同段为单位。而有些则是代表了整个cache实例,即便该实例同样是配置过key cache段的。例子如下: 预先加载索引到cache中
如上例,我们先关闭了段,那么每个cache实例只有一行。随后我们为hot_key_cache开启了段,这个cache实例仍然会存在一行,但是还会多出几行同名的独立段。我们可以通过unused_blocks大小轻松地判断:每个段大小都是4043,4个段加起来正好是一个实例大小:16172。
等待直到服务器正常活动读取到索引块时再加载它们到cache中会不大方便。于是我们可以在服务器启动时预先加载索引到cache中。LOAD INDEX INTO CACHE语句可以用来预加载所有表上的索引到其关联的cache或者是默认的cache中。如果是分区表,那么也可以只预加载其中的一些分区索引,例如: 对于B树索引,叶子节点会指向表中特定的行,如果那行记录长时间没有被访问,那么就无需缓存其相关的索引数据,这样的缓存也不会有什么好处,因此可以通过 IGNORE LEAVES选项,放弃预加载叶子节点,只有在数据被访问的时候才会加载到cache中。如果key cache不足以放下整个索引,只能存放经常被访问的部分,那么这个选项将会非常有用。并且 它还具有减少加载操作时间的效果,示例如下: Aria page cache
aria 把索引页缓存在一种叫做 page cache的数据结构中。和myisam key cache很像,但是又多了一些其他的特性。
本节主要讲解两种cache的数据结构不同以及如何使用page cache。
和myisam一样,Aria索引按块进行组织。块的大小由 aria_block_size服务器变量进行控制。此变量会影响到索引文件和page cache,因为所有的块大小必须相等。如果变量被更改,那么所有的aria表需要重建。更改此变量需要进行以下步骤:
1 导出ariab表
2 关闭服务器
3 更改配置文件
4 重启服务器
5 删除或清空所有的aria表
6 从dump文件中还原数据
aria page cache 有且只有一个实例并且不能被分割。至于myisam中的其他特性,则都可以被aria page cache支持,配置变量名称和myisam的也非常相似。
page cache大小可以通过 aria_pagecache_buffer_size变量配置。如果没有使用aria表,这个值可以设置为0.记住,默认情况下,aria表是用来做内部临时表的。(--aria-used-for-temp-tables 启动选项)
LRU机制可以通过aria_pagecache_division_limit和aria_pagecache_age_threshold变量配置,这两个变量对应于 myisam上的 key_cache_division_limit和 key_cache_age_threshold。
由于aria key cache不支持多实例和分段,因此也就不需要存储统计信息到单独地表中了。不过仍然可以通过一些状态变量来获取aria的统计信息,比如下面这样的查询
show status like 'aria_page%';
下面这张表展示了可以返回哪些变量信息以及对应在key cache列中的变量值,其含义在前面已经讲过,此处意义相同,不做赘述。 query cache
query cache存储了针对服务器执行的查询和结果信息。如果用户发起了一个查询,正好这个查询在cache中可以找到,那么结果就会立即被返回。当然,服务器仍旧会检查当前账户是否有权限执行这个查询。
query cache如何严重地影响服务器性能取决于负载情况。在许多情况下负载都能导致性能损失。query cache默认情况下是开启的,但是DBA仍旧需要根据具体的应用程序进行基准测试来决定是否启用此功能。如果应用程序有所变动,那么基准测试也应该定期重复进行。query cache也可以根据需求激活它,也就是说query cache可以关掉,但是如果特殊的指定了需要使用query cache的查询,那么该查询仍旧会使用query cache。如果使用query cache对性能有负面影响,则此策略非常有用。但是可以被cache大大加速的查询就非常有限了。query cache通过互斥进行保护。互斥就是相互排斥(。。。原文确实有这句);就是一种锁,mariadb用它来保证两个线程不能同时访问相同的资源——这里的资源指的是cache。如果数据库的并发性很高或者cache很大,那么这种锁就会拖慢数据库速度。同时query cache也会使内存分配上更为复杂,比如说为了更为重要的data或者key cache之类考虑就不得不把query cache配置得小一点。
任何对表结构和数据的更改将会导致cache中与该表相关的query cache失效。 即使多个缓存的查询包含许多结果,哪怕是对其中一个基础表的最小更改也会使读取它的所有查询失效。如果一个无效的查询可以同时被多个连接执行,那么多个连接将会尝试再执行该查询并且同时重新缓存相应数据。这种问题叫做 miss storm或者 cache stampede。
如果数据经常被更改,那么query cache几乎对性能没什么提升。如果数据很少被更改,比如仅在夜间或者一周变更一次,那么配置query cache会使得查询结果立即被返回。
在Galera 集群和OQGRAPH存储引擎使用时,query cache必须关闭。如果包含SPIDER表也应该避免使用,但是对于远程服务器可以配置。Galera第十二章的时候我们再讲,SPIDER第十一章会说到。
用户发送到服务器的查询语句仅在文本完全匹配到cache中的内容时才算命中。如果大小写不同,空格甚至注释之类有不同都会导致两个查询语句解析结果不同(和oracle一样),比如这样: 此外,仅当查询使用相同的默认数据库,默认字符集,SQL_MODE和协议版本时,查询才会匹配。 如果使用API(而不是PREPARE SQL语句)发送prepared 语句,则可以对其进行高速缓存。prepared语句只能与其他prepared 语句匹配,并且必须使用相同的参数.子查询不会被缓存,但是它们有特殊的cache结构。 包含子查询的外部查询可以被缓存。
以下几种情况查询语句不会被缓存。
1 语句生成警告
2 存储过程内部执行的
3 使用了临时表或者用户定义的变量
4 包含不定函数,存储函数,或者UFDs
5 使用了系统表
6 如果使用了以下几个子句:INTO OUTFILE,INTO DUMPFILE,LOCK IN SHARE MODE以及FOR UPDATE.
7 不涉及任何表,比如select version();
8 引用的帐户具有列级权限的表
9 使用了特殊的存储引擎,比如spider这种。
如果 query cache开启了,也可以使用如下语法强制该查询不被缓存
select SQL_NO_CACHE
如果query cache 关闭,可以使用如下语法强制缓存该查询
select SQL_CACHE
query cache的内容会被服务器进行更改,一段时间之后内存就会产生碎片,为解决此问题,可以通过以下命令:
FLUSH QUERY CACHE;
如果由于一些原因cache中存放了被无用的查询语句缓存的内容,则可以通过以下命令清空cache。
RESET QUERY CACHE;
配置query cache
如果使用了query cache,那么配置它就显得尤为重要。最好还是反复测试一下query cache对服务器性能的利弊。关于query cache的具体参数如下:
1 query_cache_type:开启或关闭query cache。0或者OFF为关闭,1或者ON为开启,2或者DEMAND仅在之前解释的例子中启用(就是可以强显式缓存某些语句)。如果是用在启动选项中,则参数值必须为数字。
2 query_cache_size:定义query cache大小。默认为1MB。0代表关闭,但是此时还应指定query_cache_type以防止服务器检查查询是否可缓存,并使用互斥锁保护缓存。不能配置太小的值,此值将四舍五入为最接近的1,024的倍数。
3 query_cache_alloc_block_size:cache中使用的内存块大小。此值可以被随时更改。大一点可以减少碎片化保证缓存速度更快,但是也会浪费一些内存。
4 query_cache_limit:限制最大可以缓存的查询结果集的大小。如果一个查询返回的结果太大,则不会被缓存,此变量非常重要,因为它会阻止少量的大结果集查询来浪费cache。
5 query_cache_strip_comments:此变量设置为ON那么会导致查询在缓存之前会删除提示。此时不同提示但是其余相同的查询算做匹配的。注意,如果使用的客户端或API的进行剥离注释,则非必须。
query cache状态
如果想获得query cache的状态信息,则可以通过如下操作完成 QCACHE_FREE_BLOCKS和Qcache_free_memory代表了空闲内存数,计量单位为块和bytes。如果query cache使用率非常高但是这两个值不高,说明cache中产生了碎片,此时应使用FLUSH QUERY CACHE进行碎片整理。
Qcache_total_blocks代表了内存块的总数,包括使用的和没使用的。 Query cache使用可变大小的块
Qcache_hits:代表了用户发起的查询语句与cache中缓存的语句的匹配情况,即命中率,应该保证此值非常高或处于增长状态。
Qcache_inserts:代表添加的cache中的条目数
Qcache_lowmem_prunes:从cache中删除的条目数, 如果这些值很高,查询缓存的效率会降低。
Qcache_not_cached:不能被缓存的查询语句的数量
Qcache_queries_in_cache:当前缓存的语句的数量。
mariadb同样提供了 query_cache_info 插件。此插件默认未开启,可以通过以下命令安装: 一旦安装完毕,query_cache_info表就会被添加到 information_scheam数据库中。其存储了query cache中的条目信息,包含如下几列:
1 statement_schema:当语句执行时选择的数据库
2 statement_text:缓存的查询语句
3 result_blocks_count:存储结果集的内存块的数量
4 result_blocks_size: 内存块的大小
5 result_blocks_size_used:存储结果集的内存总量
现在,我们将会看一个例子,首先清空cache,我们执行show status语句并使用空的query cache来查询query_cache_info表。随后我们执行一个简单的查询并执行同样的操作观察一些细微的变化。操作如下: subquery cache
正如之前所有,子查询会被缓存到一个独立地数据结构中——subquery cache。 如果某些子查询出现在不同的外部查询中,这是非常有用的,subquery cache在mariadb5.2中引入,并且默认开启。如果想关闭可以使用如下命令:
SET @@global.optimizer_swithc='subquery_cache=off';
有两个状态变量提供了关于subquery cache的效率信息。subquery_cache_hit变量代表cache命中的子查询的数量。subquery_cache_miss变量则是没有命中的数量。命中率根据以下公式计算: 对于每一个需要被缓存的子查询都会创建一张memory 临时表。这张表会存储子查询的结果集及其参数。如用来排序和分组的内部临时表一样,subquery cache表受 tmp_table_size和max_heap_table_size系统变量影响。如果超过其中任何一个变量的限制。cache的具体行为取决于命中率,如果命中率小于0.2,则关闭cache。如果小于0.7,则memory表被清空但是其结构得以保存并会在以后重新填充。如果大于0.7则表会写入磁盘。
替代查询缓存方法
有个query cache的问题就是对表的频繁更改会让缓存的查询无效,使得性能难以预测并且会导致cache stampedes。因此,取决于负载,我们想采用一种query cache的替代方法。
有时候很少的结果集会造成很高昂的开销。典型的例子就是在大表中使用聚合函数。这样的查询通常生成统计数据,并且可能不需要总是随时更新。例如,如果一个查询关联了所有以月份为分组的聚集数据(比如上个月的销售情况),这样的结果对包含今天的数据并不重要(有时甚至是一周或者一个月)。这种情况下,工作负载可能受益于汇总表,汇总表就是定期清空并使用查询结果重新填充的表。填充汇总表可能非常慢,但是查询该表则非常快。
把所有数据聚集到一张汇总表中可能就足够了,不同的查询检索其中的一部分数据。或者可以把不同的结果集存放到不同的表中,再或者混合使用上述两种策略。
在许多情况下,查询生成html报告或xml文档。其中的一些报告并不会每小时或每天进行变更。或者即便有变更也是不相关的内容。如果报告预计在一段时间内保持不变或被允许这样做,那么整个报告可以被缓存到表中。这可以提高应用程序效率,特别是如果更新报告会涉及昂贵的查询的时候。
一般来说memory表是最佳的选择。当然,如果服务器崩溃数据则会丢失,但是表可以重新填充,因为非聚集的数据已经都写入了磁盘。还有另一种用来存储汇总数据或者报告的方法就是使用key-value软件,这些软件对于此类查询进行了优化。最常见的就是memcached。其优点之一就是可以对存储的值设置超时时间。这允许我们在存储在基础数据更改时未失效的过时数据,但是在给定的时间后会自动消失。 然而,当数据到期时,可能发生cache stampede的问题。因此对于许多负载情况,这样的方案并不能解决query cache中的主要问题。
table open cache
当线程需要访问表的物理文件时会需要一个文件描述符。为加速文件访问,mariadb会缓存这个文件描述符到table open cache中。如果同一数据库中包含了许多myisam表时会非常有用。在cache中搜索一个文件描述符是会产生开销的。如果缓存不足够大以放下所有需要的文件描述符,那么最好关闭它。DBA最好做一些测试来决定table open cache是否适用于当前的负载情况。
table_open_cache服务器变量决定了可以缓存多少个文件描述符。这个值不要超过操作系统中允许的最大文件打开数量,否则服务器会拒绝新的连接。在unix系统上,这个值可以通过如下命令获得
ulimit -n
别的系统上,如果ulimit命令不可用,那么就去查看系统文档。
对于缓存所有需要的文件描述符的数量,table_open_cache应该等于允许的最大连接数(max_connections变量)*包含关联最多表的查询语句涉及到的表的数量。另外别忘了再加上一些临时表。对于myisam和ariab表会缓存其索引文件的文件描述符,但是这些描述符可以被所有连接所共享。
如果服务器报出了不能打开更多文件的错误,很可能是 mysqld_safe --open-files-limit启动选项设置的过小。
per-session buffers
mariadb有几个per-session buffer以加速一些查询。如果这些值太小,一些复杂的查询则会花费很长时间。如果设置的非常大,那么在大并发情况下又会浪费大量内存。DBA应该根据业务把该参数控制在合理范围之内。
当应用程序打开一个连接的时候,应该大致知道发起的是何种语句。很可能不同的应用程序连接执行的是不同的任务。比如web应用程序那种,大多数连接执行一些简单查询,还有一些执行更复杂的任务,也就需要更多的资源。
per-session buffer 可以基于每个会话单独配置:一个连接可以改变该值大小而不至于影响其他连接。也就是说它允许为一些会话配置大的缓存,为一些简单的查询配置小一点的缓存。
在会话级配置 per-session buffer,连接需要发起如下命令: 如果为所有连接设置默认值,操作如下: 但是,即便很好地配置了该参数,内存依旧会在许多连接不需要该缓存而又不及时关闭的情况下产生浪费。开发人员应该确保连接在不需要的时候及时地关闭。或者设置一个会话超时时间。当连接空闲时,在一定时间后关闭该连接。此时间可以通过wait_timeout参数进行配置。如果他们超过一定数量,我们也可以强制服务器拒绝连接,此数量由max_connections进行控制。为此变量设置适当的值可以保证服务器免遭拒绝服务攻击。但是在做此工作之前我们必须明自己的工作负载情况,以防一些用户无法连接到数据库。
以下几个参数影响了per-session buffer的行为
1 sort_buffer_size:用以加速order by。如果show session status展示的sort_merge_passes太高,则说明此值太小。
2 read_buffer_size:优化myisam表的顺序扫描
3 read_rnd_buffer_size: 加快了使用多范围优化策略执行的查询和从MyISAM表的所有随机读取。
4 join_buffer_size:使用batched key访问策略优化join
5 bulk_insert_buffer_size:加速多行插入(包括load data infile)到myisam表。
总结:
本章我们讨论了mariadb的缓存。最重要的就是存储引擎使用的缓存。我们用了大量笔墨描述了innodb buffer pool,因为它更复杂也更常用。innodb buffer pool会缓存数据和索引,myisam和aria只缓存索引。
query cache有时是一个非常有用的解决方法,因为它可以立即返回需要的结果集。同样,subquery cache可以让子查询立即返回结果集。然而在典型的OLTP中,数据经常会失效,所以是否使用有待商榷。我们还学习到,此cache非常有用但是需要dba进行反复测试以保证可用。我们 还讨论了query cache的替代方案。
还讨论了table open cache,此cache用来避免服务器太过频繁地打开关闭文件。
在最后一节,讨论了关于per-session buffer的内容。
下一章我们会介绍innodb 表压缩技术,以及如何在缓冲池中处理压缩数据。 第七章 innodb 表压缩
大多数数据库都有一个共同的特性:数据都是增长的。通常意味着这个库的价值越来越高,对用户也越来越有用。但是这也会导致一些问题。本章将会讲解用来减小数据库的物理文件体积的特性:innodb表压缩。
innodb无需使用特殊工具,仅通过sql语句即可完成表压缩过程。压缩过的表和未压缩的一样,可以正常读取写入数据。这个特性能不能提升性能还不好说,但有一点是肯定的——它节省了磁盘空间。
本章涉及以下几点:
1 使用innodb压缩前提
2 创建innodb压缩表
3 实施压缩
4 监控压缩表性能
5 其他的压缩解决方案
innodb 压缩技术概述
innodb支持表压缩技术,使用这种技术一般基于以下两点考虑
1 节省磁盘空间
2 减少I/O
如果我们使用的是SSD,那么第一个原因非常重要,毕竟太多的I/O会减少SSD的寿命.
在许多负载中,磁盘的I/O会是性能的瓶颈.减少数据文件体积可以明显减少需要把数据从磁盘读取到buffer pool中的I/O次数,反之亦然.
然而,innodb压缩技术会有一些开销。从磁盘中读取的页在加载到内存之前需要先进行解压。内存中被修改的数据在写回磁盘之前需要进行压缩。因此会对CPU产生负担。故使用压缩技术会使性能下降。
如果内存足够,innodb会在buffer pool中保存压缩过和未压缩过的两个版本的数据。这就意味着要么buffer pool足够大,要么数据足够小。 In this case, useful pages can be evicted from the buffer pool so that it contains compressed pages(这句话作者写的前后不搭,也无关紧要。)因此,innodb压缩技术在读繁重的服务器上有优势,而在写繁重的服务器上,最好不要使用。
还有个重要问题就是要压缩表数据的效果。innodb使用LZ77算法进行压缩。这种算法非常适合文本类型数据,对数字效果一般,对于已经压缩过的数据格式,比如图片,mp3,视频没有效果。
对于TEXT,BLOB,VARCHAR类型的数据,有必要的话最好单独存放。如果一个页面不能放下一行数据的话,那么最大的列将会被移动到一个叫做overflow pages的特殊结构的页。移动这样的页将会影响到所有的操作。如果使用表压缩技术可以减少overflow pages的数量,那么使用它是最好不过的了。
熟悉innodb压缩技术是非常重要的,唯一一种判断该技术是否适用于当前工作负载的办法就是去测试它。要基于真实的负载情况,并且通过监测一些 information_schema表。
使用innodb压缩前提
为了使用innodb 压缩技术,有两点硬性要求需要满足。
1 每张表必须存储在单独的文件中
2 必须使用 Barracuda文件格式
下面我们开始讨论具体内容以及如何实现。、
浅析 file-per-table 模式
innodb表存储在表空间中,其包含了数据和索引,在老版本的MariaDB中,默认情况下,innodb只使用一个表空间——系统表空间。它同时也包含了change buffer,doublewirte buffer以及undo 日志。这个表空间存放在MariaDB定义的数据目录中的ibdata文件中。然而在最新版的MariaDB中,系统表空间仍然可以存放这些,但是最新系统支持每张表单独存放到一个表空间中,在创建表的同时会创建出相应的以.ibd为扩展名的表空间文件。存放在datadir中(注意数据库目录存放在datadir中,是datadir的一个子文件夹)。这种存储方式叫做file-per-table模式。MariaDB 10 中默认开启。
file-per-table默认开启,如果想关闭它,可以设置 innodb_file_per_table变量。当表被创建时,innodb会校验该变量以决定是存放到系统表空间还是单独地表空间中。
让我们看个示例,在MariaDB中创建一些表: 现在,来查看一下datadir和数据库目录的内容 除了可以看到系统表空间,在test_innodb文件夹中可以看到
1 数据库选项文件db.opt
2 表结构定义文件 .frm
3 do.ibd表空间文件
当创建haon和tri表的时候,innodb_file_per_table=off,这两张表会存放到系统表空间中并不会为它们创建单独地表空间。当创建do表的时候,innodb_file_per_table=ON,因此会有一个单独地do.ibd表空间文件。
innodb文件格式简述
innodb很早之前使用一种叫做 Antelope的文件格式。为向后兼容考虑,新版本中仍旧兼容此格式。系统表空间使用此格式。新版本中出现了Barracuda文件格式,其支持更多特性。表压缩技术只能在Barracuda文件格式上使用。
//*两个文件格式名字源于英文单词表的字母。新的表格式仍旧会保持这种命名规则,即下一版是以E开头的动物名称命名(C去哪儿了)*//
当 file-per_table 模式开启且创建新表时,innodb会根据innodb_file_format变量决定创建表的文件格式。在information_schema数据库中的 innodb_sys_tables 和 innodb_sys_tablespaces表中有一列叫做 FILE_FORMAT,其代表具体表使用的具体文件格式。
看一个创建两种不同文件格式的表的例子: 不同的文件格式代表着不同的行格式。为使用barracuda,我们在创建表时必须特别声名行格式是 DYNAMIC还是COMPRESSED。默认行格式是COMPACT,是Antelope文件格式定义的行格式,如果行格式是这个,说明没有使用 Barracuda。
//*如果是在MySQL或者MariaDB 5.5版本之前使用复制环境,则需要去设定innodb_file_format=‘Antelope’,确保没有表使用 Barracuda。
*//
现在来查看 INNODB_SYS_TABLESPACES表: 当使用 antelope格式时,information_schema中的表不会告诉我们表到底使用了什么行格式(很明显,最开始定义这个格式的时候还没料到后面会出现新格式)。如果是用了Barracuda的压缩表,则在ROW_FORMAT列中出现的应该是COMPRESSED.
//*表可以在系统表空间和自己独立地表空间之间互相移动,这个时候就需要注意 innodb_file_per_table和innodb_file_format的设置,在发起alter table命令时需要尤其小心。此两个变量仅存于全局变量中。这就表示有一定的风险,如果当前连接并不是唯一拥有SUPER权限的连接。谨防一个线程更改变量,同时另一个线程操作alter table这种情况发生。*//
barracuda文件格式在MariaDB 5.5和mysql5.5时引入,也就是说,如果使用复制环境,某一端版本早于5.5之前的话,不能使用barracuda格式,也不能使用压缩特性。
创建innodb 压缩表
在创建压缩表之前,通常最好确认一下服务器的SQL_MODE是否处于strict 模式,理由是如果处于strict模式,innodb会更详细地检查create table语句。如果有些表定义写错了,表就不会创建并且抛出错误。这会防止我们因为手抖创建出意料之外的表结构。但是,已存在的应用会依赖于现有的sql语句,如果在strict模式,可能会阻碍应用程序正常运行。因此,strict模式默认是关闭的,并且使用时最好在会话级开启。如果想全局开启innodb的strict模式,执行如下命令:
SET @@GLOBAL.INNODB_STRICT_MODE=ON; //* 在当前版本中,用strict 模式来检查创建压缩表的步骤是唯一会用到的地方。但是,在未来的版本中,它可能会用在更多的地方,不仅仅是创建表。因此,除非它会导致我们应用程序产生错误,否则最好还是一直开启 *//
在检查了 INNODB_FILE_PER_TABLE和INNODB_FILE_FORMAT变量之后,我们就可以开始创建innodb压缩表了。很简单,在建表语句后面跟着COMPRESSED子句即可 对于压缩表,索引块的大小可以根据每张表进行单独配置。通过使用KEY_BLOCK_SIZE选项即可。此选项仅在使用压缩表时有效,因此,如果使用了这个选项,那么COMPRESSED子句就可以忽略掉了。 索引块的大小不会影响到表的压缩级别,压缩级别不能根据每张表单独进行设置。然而通过更改页大小,我们可以决定一页可以存放多少行数据。允许的值为 16K,8K(默认),4K,2k和1K。0代表默认值,但是在这样设置,就不会使用表压缩了。通常KEY_BLOCK_SIZE会小于INNODB_PAGE_SIZE(默认16K)。然而,如果表包含TEXT或者BLOB列, 一个16 KB的内存可能允许在常规页中存储许多值,因此它们不会存储在offset pages偏移页中(如第6章缓存中所述)
有时我们会设置一个不符合要求的KEY_BLOCK_SIZE值。这种情况下,innodb的strict模式处于开启状态则尤为重要,因为它会强制MariaDB报错。如果没有开启该模式,表就会被创建,但是随后在插入数据或者update的时候就会报错。
选出最佳的KEY_BLOCK_SIZE值就是创建数据相同结构相同但是KEY_BLOCK_SIZE不同的多张表,然后基于真实的负载情况进行性能测试。压缩表的性能可以通过查询information_schema数据库,本章随后即说到。
可以通过命令来更改压缩表的KEY_BLOCK_SIZE,比如这样:
ALTER TABLE COM_TABLE KEY_BLOCK_SIZE=16;
甚至可以将一个未压缩过的表变为压缩表: 上面两个alter table命令需要花费点时间但是因为使用 IN-PLACE算法,不会产生锁定。
INNODB 压缩表实施参数简述
通过设置 INNODB_COMPRESSION_LEVEL变量可以改变压缩表的压缩级别,可选参数为0-9 的整数。值越高代表压缩效果越好,但是性能也就越差。默认的压缩级别为 6,此变量动态可调整。
正如之前所说,当索引条目从磁盘读取到buffer pool的时候,其会包含压缩的和未压缩的两份数据结构。innodb 通过几种办法来避免大量的压缩和解压缩操作。比如,它在修改了一个压缩的页面的数据,并不立即压缩,而是在页面需要刷写到磁盘时再进行压缩。为达到这种目的,使用了一种叫做 modification log的日志结构来跟踪这些需要压缩但还未压缩的块。经常更新和插入较短的行可以不用重组页面即可完成。
需要说明的是,每个页面的 modification log都有空间限制。如果innodb尝试写入change到日志中,但是又无空间可用,页面又未经过压缩,那么记录的变更会被应用,并且页面会被再次压缩。在某些情况下,页面会在某些变更发生后变得非常大,此时数据需要重组以适应页大小。当新的页仍旧过大的时候,就会发生 compression failure错误.
重建压缩页会耗费时间,因此innodb尽量去避免频繁地出现compression failures。如果compression failure和应用变更之间的比率超过innodb_compression_failure_threshold_pct设置的值,innodb会在每个新的压缩的页的末尾留一段空余空间。 innodb_compression_failure_threshold_pct服务器变量是一个百分比值,默认为5. innodb_compression_pad_pct_max 服务器变量指定了每个压缩页保留空余空间的最大百分比。许可范围为0-75,默认是50.如果设置为0,则表示关闭此项功能。(表示不留空,注意是留空百分比。)
如果compression failures在某张表上发生的太过频繁,说明对应的 KEY_BLOCK_SIZE可能设置的太小。如果压缩表的性能很低并且经常发生compression failures,那么最好适当调大innodb_compression_failure_threshold_pct的比率。 如果常规的更新会显著增加行的大小,那么应该把 innodb_compression_pad_pct_max设置的大一些.
下一节将会讲解如果监控压缩表的性能
监控压缩表性能:
information_schema数据库中有些表可以用来监控innodb压缩表的性能。所有与之相关的表都以 innodb_cmp开头,因此可以通过以下语句列出相关表. 主要的innodb表有:
1 INNODB_CMPMEM:存储在buffer pool中关于压缩表页面的数据
2 INNODB_CMP:存储整个服务器有关压缩和未压缩操作的相关内容。
3 INNODB_CMP_PER_INDEX:此表内容和上一张非常类似,只不过是通过每张表和索引进行分组列出。下图我未使用压缩表。所以都是0. 还有一些以_RESET结尾的表,对应着前半部分名字相同的表,与之前不同的是,这种表只要一被查询,则之前对应的表内容就会被重置。此可以用来在一段时间内监控压缩表的性能变化情况。或者用来检查变量变更前后的影响。
//* 查看 innodb_cmp_per_index和innod_cmp_per_index_reset表会对性能造成很大影响,因此一般都是空的,除非设置了 innodb_cmp_per_index_enable=ON,不过建议在生产环境最好不要这么干. *//
INNODB_CMPMEM表:
此表展示了buffer pool中的压缩页的统计数据。数据以页大小为分组。每行展示了不同的KEY_BLOCK_SIZE大小的表的状态。事实上,如此设计都是为DBA方便来决定每张表的KEY_BLOCK_SIZE大小的。下面这张表展示了 INNODB_CMPMEM表的列含义(不做翻译): 当INNODB_CMPMEM_RESET表被读取的时候,RELOCATION_OPS和RELOCATION_TIMES字段会被置0。
假定我们有一张customer表,通过该表结构创建三张以不同KEY_BLOCK_SIZE大小,通过名称予以区分的表:custumers_16,customers_8以及customers_4.如之前所说,优秀的测试源于真实的负载情况。然而,在本例中,我们仅仅来看这些表如果工作,因此我们只执行select count(*) 来查看每张表。随后,我们将会查看 innodb_cmpmem_reset.示例如下: 在进行真实测试和检查过这些表的内容之后,我们将会选择一个比较小的KEY_BLOCK_SIZE,因为它不需要太多的重新申请内存的操作。
INNODB_CMP_PER_INDEX表
INNODB_CMP_PER_INDEX表以索引为分组展示了压缩页的性能信息。如之前所说,查询该表开销很大,因此INNODB_CMP_PER_INDEX总为空,除非INNODB_CMP_PER_INDEX_ENABLED=ON。
该表包含以下几列:
1 DATABASE_NAME:不用说吧
2 TABLE_NAME :也不用说吧
3 INDEX_NAME:更不用说吧,这张表就是基于索引进行group by的。
4 COMPRESS_OPS:modification log 中的变更应用到页面的次数
5 COMPRESS_OPS_OK:压缩操作成功的次数
6 COMPRESS_TIME:压缩数据耗费的时间
7 UNCOMPRESS_OPS:解压操作的次数。记住此数值不断增长,新的索引条目读取到buffer pool和压缩操作失败都会增加此数字
8 UNCOMPRESS_TIME :解压操作耗费的时间
当读取 INNODM_CMP_PER_INDEX_RESET表的时候,除了 DATABASE_NAME,TABLE_NAME,和INDEX_NAME列外全部置0.
//* 此表告诉我们多少索引会受压缩,解压缩操作产生性能的负面影响。压缩失败的次数不同于COMPRESS_OPS和COMPRESS_OPS_OK.如果该值太高,则性能太差,索引速度会减慢,因为页面会被经常写入,换出buffer pool。如果buffer pool配置和索引使用情况没有得以改善,则表将不会被压缩。
如果压缩失败太多,应尝试减少该情况。如果压缩失败发生在一张表或者一个索引上,我们应该考虑修改 KEY_BLOCK_SIZE选项;另一方面,我们可以尝试设置恰当的INNODB_COMPRESSION_FAILURE_THRESHOLD_PCT和INNODB_COMPRESSION_PAD_PCT_MAX选项 *//
INNODB_CMP表
此表和INNODB_CMP_PER_INDEX表基本相同,区别之处就在不是以索引为分组。此表很少用到,但是因为收集该数据耗费不了什么资源,所以此表总不为空。
INNODB_CMP表和INNODB_CMP_PER_INDEX表有着相同的列,唯一不同之处就是没有DATABASE_NAME,TABLE_NAME,INDEX_NAME列.
如果查询INNODB_CMP_RESET表,则它的内容会被重置.
其他压缩方案
有些存储引擎也能压缩表,比如这些:
1: ToKuDB:其上的表总是被压缩。这就是该引擎的特性。其专注于通过大量减少磁盘操写入操作提升性能。
2:ARCHIVE: 专为压缩表设计,但是功能有限,可以插入数据,不可删除和修改,索引支持也非常有限。
3 :MyISAM:普通表不会被压缩,需要通过特殊的工具——myisampack来压缩表。并且压缩过后只支持读取
//* Aria引擎功能比Myisam更强大,但它不支持压缩 *//
其他的压缩方案超出本书范围,并且也不像innodb使用的那么频繁,比如tukudb,在很多负载情况下都不如innodb性能高。archive引擎和myisampack工具非常容易使用,但是在大多数情况下,因为限制太多达不到技术要求。虽然tokudb引擎在压缩方面同样具有很强的灵活性。然而tokudb并非是一两句能说清楚的,并且默认并不开启,本书不涉及此内容。
但是作为DBA,知道这些东西存在还是很有必要的,下例中展示了不同引擎下的不同的压缩表的情况: 上面的输出已经通过格式化展示以方便阅读,但是结果是真实的。当然, 有时候我们会得到完全不同的结果, 所以如果与我们的生产环境情况相关,最好提前做好的测试。但是该表内容仍旧代表了一类真实情况:包含许多短的文本字段,自动提交键,以及相应索引。
上例中的数据文件如下:
1 customers_non_comp.ibd:innodb表,但没压缩
2 customers_*.ibd: 通过不同的KEY_BLOCK_SZIE进行压缩的innodb表
3 customers_arch.arz ARCHIVE 表
4 customers_myi.myd 和 customers_myi.myi:压缩过的myisam数据和索引
在这个例子中,压缩过的myisam文件会比压缩得最好得innodb表空间还大一点,最终archive压缩效果最好.(他说的是myisam的数据+索引的体积)
可以看出archive和innodb的压缩比很高,然后我们就得出结论使用archive是最好得选择了吗?NO!看以下几个原因解释为什么innodb这么有用:
1 archive 不支持事物,不过对于大表做OLAP不错
2 innodb 插入性能非常好,Xtradb更好
3 archive不支持像 LINESTRING或者POLYGON这样的geometric数据类型
4 压缩表虽然不会经常被读取,但是我们还是想加上索引,archive只支持给主键是自增列的列增加索引其余不支持。
5 innodb和xtradb的开发者很活跃,archive有点疲软
如果以上原因都不是主要原因,那么archive是个不错的选择。至于其他情况,调整innodb_comperssion_level是个比使用archive更好的办法。
总结:
本章我们讨论了innodb表压缩技术。包括在哪种场景下使用,以及是否真的适用于我们的负载情况。
我们学习了如果创建压缩表和配置innodb压缩参数。然后。我们讨论了如何监控innodb压缩表性能。
在讨论这些内容的同时,我们还介绍了innodb其他的重要特性:file-per-table(每张表自成一个表空间文件)模式和innodb文件格式(仅有barracuda格式可以使用压缩表)
MariaDB还提供了一些其他非常有用存储引擎。因此在最后的部分,我们比较了innodb压缩方案和其他引擎的方案。
下一章中,我们将会讨论备份和恢复技术,以及如何应对数据损毁。 第八章 备份和灾难恢复
如今,越来越多的商业系统,包括企业活动,营销流程基本上都需要通过应用程序来操作数据库中的记录。如果发生数据丢失,那么公司的一些正常活动可能就会中止,除非数据正常恢复。如果数据丢失不可复原。公司很可能因为丢失数据而造成巨大的损失。这种数据丢失对公司来说称之为灾难一点也不为过。用行话来讲,尽可能地恢复这些数据叫做灾难恢复。因为并不是总能恢复得了数据,因此非常有必要去制定数据备份策略以便在灾难发生之后迅速准确地进行恢复。本章将会讲解备份和灾难恢复。这对DBA来说是一项十分重要的工作。
本章将会涉及以下几个主题:
1 备份类型
2 通过mysqldump进行逻辑备份
3 通过文件系统进行物理备份
4 通过 percona xtrabackup进行完整和增量备份
5 如何还原
6 如何修复损坏表
备份类型
有些问题会导致数据损毁或丢失,原因有以下几种可能:
1 磁盘写入时突然断电
2 硬件错误(比如磁盘或主板)
3 操作系统崩溃
4 MariaDB或存储引擎BUG(和别的程序一样,MariaDB也有bug) 人为也会导致发生数据损毁。比如黑客使用软件漏洞来摧毁数据。或者可能是我们手残敲了 DROP DATABASE删了不想删的库。
没办法确保这些事故绝对不会发生,相反,有可能发生的就一定会发生。因此我们需要通过备份数据来时刻准备着还原那些重要的数据。
可以通过几种方法来进行备份。其中各有所长,无一独大。选择哪种备份要取决于很多因素。在制定备份计划之前,我们需要考察如下几个问题:
1 数据的重要性
2 数据更新频率
3 是否可以在备份过程中临时停机?
根据需求,我们才能清晰地去选择最适用于我们的备份策略。
逻辑和物理备份
逻辑备份会创建相关数据的备份文件。比如CSV格式的文件会包括所有的值,text类型的会包含大量的重建相关数据需要的SQL语句。它们统称为dump文件
物理备份指的是从物理级别复制数据文件。要求就是在备份完成之前,MariaDB不能往相关文件写入任何数据。这对于myisam这样的存储引擎来讲还好说,但是对于结构复杂的innodb,则很难实现。在mariadb10之前的版本中,想要备份innodb表,就不得不完整停机。物理备份会复制整个数据目录。默认情况下会包含日志和配置文件。
有些存储引擎,比如myisam,把表都存放到一个文件中;其他的则不是,比如第七章我们说到的innodb可以把每张表单独存放到一个文件中。如果表被分散存放,就可以只备份有关联的数据,而不是所有表。这对于在有些很少(几乎不会有)变更的表或者可以轻松通过其他表进行重建的表来说非常实用。
对于分区表,每个分区都会存储在一个单独的文件中。一般最新的分区包含了最近的数据,而其他分区包含历史数据。例如,某个分区存放着上个月发生的交易记录,其他分区存放着更老的记录。因为历史数据我们之前肯定备份过了,因此现在只需要备份一个最新的分区即可。分区我们在第十章再说。
逻辑备份要点
1 数据库不需要完整关闭。然而,繁重的事物会影响事物表性能,非事物表会被锁定。因此此种备份通常会因为密集的读操作而减慢服务器性能
2 逻辑备份灵活多变,定制起来非常容易。比如,可以在恢复数据之前通过修改dump文件来变更数据库的名字。或者可以删除其中的一些行。
3 逻辑备份比物理备份的选择性更强。它基于sql语句,可以通过复制语句来控制一些行或列。
4 逻辑备份可以恢复到MariaDB更老的版本上去。比如,在升级MariaDB时应该创建一个逻辑备份以备发生错误。
物理备份要点
1 物理备份速度更快,因为它直接通过操作系统进行复制。体积通常也比较小,因为其包含的数据和索引已经是紧凑排列的。
2 物理备份通常需要包括日志和配置文件。虽然不是恢复数据的必备,但是如果丢失了配置文件和重要的日志应该也算得上一种灾难吧。
热备和冷备
定义:服务器运行时进行的备份叫做热备,停机时进行的备份叫做冷备
所有的逻辑备份都是热备。无法通过不检索MariaDB数据来进行逻辑备份
对于MariaDB 10来说,可以在备份期间锁定物理文件,因此无需停止服务器。然而对于老版本,对于innodb文件只能进行冷备。在热备期间,服务器仍旧可以接收来自客户端的命令。
但是我们知道备份会花费很多时间,并且会把客户端的请求进行队列处理。或者如果允许服务器在一段时间内停机,比如说下班后。此时,如果我们想进行物理备份,我们更倾向停机进行备份。一来无需停止锁表,二来整个过程会更直接。
完整和增量备份
如果需要备份的数据量非常大,那么备份会花费很长时间,在整个备份期间,表很可能会被锁定。同时备份也需要大量空间。为减少备份时间和占用的空间,我们可以使用增量备份。增量备份就是只备份自上一次备份一来变化的数据。
当然,在还原时肯定不想还原自服务器首次启动以来已经执行所有增量备份。虽然理论上可以,但是会非常慢,并且需要大量的空间来存放备份,关键还容易出错。因此,常规的完整备份仍然是很有必要的。
然而,最好混合使用完整备份和逻辑备份以制定优秀的备份策略。比如,我们可以每周进行一次完整备份以及每天晚上进行增量备份。如果想在灾难发生后还原数据,我们需要还原最近的完整备份以及其后的所有增量备份。
备份和复制
备份和复制是两个相关的主题。它们都是通过保存双份数据来解决灾难后的数据恢复问题。
然而!复制不能替代良好的备份策略。事实上,这两种技术在概念上完全不同。备份是数据的静态一致快照,它永远不会改变。复制是slave端重现master端进行的所有操作,因此数据会随master端改变的。
复制环境中,我们可以选择在哪里进行备份操作。最好别在master端进行备份,因为master端用来接收客户端请求,应该尽量避免拖慢甚至影响master端的正常工作。最好是在slave端进行备份,就是那种复制环境末端的服务器。然而,复制环境中的master端和slave端会有一个延迟,延迟甚至可能达到几小时或者几天。虽然这对于复制是可以接受的。但是备份应该是包含最近的数据。因此,如果对于复制延迟并不敏感的话,可以在slave端进行备份。复制技术我们第九章再说。
数据库集群是非常复杂,高可用的复制环境。在第十二章MariaDB galera cluster 中。我们将会讨论MariaDB中最常用的集群解决方案。galera会保证数据在所有的节点同步更新。这种情况下,如果其中的某个节点没有用来接收客户端请求,那么从该节点进行备份是个不错的选择。或者,我们也可以选择硬件性能最强劲的那台服务器。
备份前的准备工作
到此,我们已经讨论了备份的重要以及各种备份的特点。现在我们将会讨论实施这些备份的详细步骤。但是在讨论具体细节之前,先问个重要问题:在我们选择了具体的备份策略之后,我们应该怎么办?
对于每一种备份方法,我们都可以按照以下的步骤进行
1 编写必要的脚本
备份需要自动化机进行,因此我们需要编写cron任务或者是其他的以使备份定期自动执行的方法。
2测试备份的数据
可以使用开发环境测试。比如先部署上测试数据,进行一次备份。检查备份是否顺利完成。以及整个备份的时间是否在我们可接受的范围之内。
3 测试数据还原
假设截至到现在我们的数据已经被大量修改过,然后我们想通过备份进行还原。需要先检查一切是否都已经就绪(包括准确的备份路径,数据文件夹,my.cnf参数文件路径等)。当灾难发生后我们知道准确的信息是非常有用的。这可以使得我们的还原过程进行的又对又快。
4 对整个过程编写文档
如果我们没有记住如何使用,即便是最好的备份还原策略都是无用的,因此把整个过程文档化是非常有必要的。
5 切换到生产环境
当我们真的做好准备的时候,这一步就可以在生产环境上进行了。
本书是关于MariaDB的。并不涉及cron job,shell命令,程序或者是测试方法等内容。在所有的章节中,将仅讨论主题的核心内容:如何进行备份和还原。但是当把这些技术应用于真实环境时,最好遵循之前提到的几个步骤以便保证整个过程都会按照我们的预期进行发展。
通过mysqldump创建dump文件
mysqldump是用来创建热逻辑备份最有力的工具。所有的MariaDB的发行版中都包含该工具。可以在bin文件夹中找到。(最好配上环境变量)
通常,mysqldump用来创建一个dump文件:它会连接到MariaDB,读取需要备份的数据并创建一个包含可以还原所有数据的sql语句的文件。它由几个选项可以让我们过滤不想要的数据。dump文件也可以进行手工修改以应对真实的需求。由于生成的是可执行的sql语句,因此可以在更老版本的MariaDB, MySQL甚至是其他DBMS中进行还原。
由于所有这些原因,mysqldump是一个非常灵活的程序。这就解释了为什么mysqldump是操作逻辑备份的最佳工作。它可以应对多种不同的需求,比如从一台服务器复制一个数据库或者一张表到另一台服务器中或者是生成dump文件以让应用程序安装过程中自动执行。
转储dump文件的缺点是它们会占用大量空间。因为这种备份出来的文件数据不仅没有压缩,而且还多了一大堆insert语句。然而,mysqldump也支持创建文本类型的备份,我们在下面的章节会进行介绍:
取决于是否备份服务器中的所有数据库,mysqldump支持三种不同的语法。比如备份部分数据库或者是数据库中的部分表。命令如下: 以上三种情况备份出来的dump文件都不会包含use命令。因为用户很可能想要在别的数据库中重建备份的数据
当导出多个数据库时,如果想排除某些表,可以使用如下命令
--ignore-table-db_name.table_name
如果想排除多个表,则必须多次编写该选项。注意不要使用逗号进行隔开,使用空格即可,比如
--ignore-table_db_name.table_one --ignore-table=db_name.table_two ...
mysqldump 使用和MySQL命令行客户端一样的方式进行连接。
默认情况下,dump文件会包含creata database语句。如果想跳过它,可以使用--no-create-db 选项。通常我们会在create database之前有一个drop database的动作。因此,如果数据损坏需要通过正确的数据进行完整替换,可以使用--add-drop-database选项。默认所有的表定义和数据都会包含在dump文件中,但是如果使用了--no_create_info选项,则不会包含表定义,--no-data选项不会包含表数据。在create table语句中的表选项语句并非是标准的,并且仅在指定了 --table-option选项时才会有效(表选项在MariaDB中可能正常执行,但是在MySQL中不好说)
有时候我们不想通过dump来删除和替换已有的数据库,因为该库可能包含我们想保留的表。此时,我们想到了可以针对表来进行完整替换。此时我们可以使用 --add-drop-table选项,它会在每个create table语句之前添加一个drop table命令。另一方面,我们很可能想让数据通过replace而不是insert进行插入:dump数据将会替换已有数据,但是如果表中包含dump文件中没有的行,那些行将会被保留。这可以通过 --replace 选项完成。还可以使用--insert-ignore,它会把insert转换成 insert ignore 。这可以用在导入数据时只插入表中不存在的数据。
//* 注意replace和insert ignore的主要不同,replace会删除已有数据,而insert ignore会忽略已有数据。有个重要的概念叫做 auto_increment。replace会删除原有数据然后进行插入,虽然数据部分完全相等,但是自动提交列已经不一样了。如果外键不用于保留交叉表数据的完整性,则会导致一些问题。如果使用了外键,那么replace 过程会很慢 *//
默认情况下不包含程序部分,比如触发器,过程,函数,等。然而,一个完整的逻辑备份应该包含这些内容。并且也用不了多大空间。如果想在dump中包含这些内容,可以使用 --triggers , --routines 和 --events 选项
通常我们想在一个事物中完成整个dump过程。这样可以保证数据的一致性。如果我们指定了--single-transaction 选项,mysqldump会在 repeatable read 隔离级别开始事物,并在完成dump过程后发出commit命令。--no-autocommit 选项会使用set autocommit=0 和commit 对转储文件中的每个表进行插入。这样还原更快,但是当插入其中一张表时,其他表可以被其他会话修改。
当dump一个非事物表时,--single-transaction 选项无法保证数据一致性。因此,这时候我们需要使用--lock-all-tables选项来锁住表。此选项会在所有数据库上获得一个全局读锁。它会阻塞服务器上的所有写操作直到dump过程完成。这也是唯一一种保证数据库中非事物表的事物一致性的方法。(拗口,其实是:只有全局锁能解决非事物表导出不一致的问题) 然而通常我们只需要在每个数据库的基础上保证数据完整性,此时可以使用--lock-tables选项,它一次只锁住一个数据库。--add-locks 选项会在每个表插入之前添加lock tables在插入之后添加unlock tables 命令。--disable-keys 选项会在还原myisam表时使用alter table .... disable keys 来加速还原动作。
//* 记住,lock tables 和 unlock tables 会在当前事物包含中隐式提交, 因此一些所提到的选项是相互排斥的,一起使用--no-autocommit和--add-lock是没有意义的,因为表锁会使事务无效。如果我们想用多行插入来替代单行插入,可以在lock table的情况下使用 --extended-insert 选项 *//
默认情况下,mysqldump从服务器中读取所有的行到buffer中然后把它们一起写入到dump文件。这是基于性能优化考虑,当导出大量数据时,它需要大量内存。为避免缓冲行(bufferizing 不知道怎么翻译,难道是类似oracle的直接路径绕过PGA?),可以指定--quick 选项。
以下是一个典型的dump文件 当出现下面这些行的时候后,数据开始真正的dump 还原dump文件非常简单。最简单的就是打开文件执行语句。有许多种方法可以完成,比如,如果dump文件很小,可以复制它的内容然后粘贴到你喜欢的gui工具中。然而最常用的方法还是让MySQL命令行来调用dump文件作为输入。以下语法可以在全平台使用,包括windows 如果现在已经处在MySQL命令行中,那么可以使用source命令来指定要导入的文件 文本格式的备份
备份一张表可以是一种特定的文本格式:以特定格式分割开所有列的人类可读的文本格式(耐心断句)。比如最常见的以逗号分割的CSV格式。MariaDB支持以下几种方式来创建文本格式的文件
1 mysqldump 使用--tab 选项
2 select ... into outfile 命令
3 CSV 存储引擎
4 CONNECT 存储引擎
至于还原文本格式的备份,可以通过如下几种方式
1 mysqlinport命令
2 load data infile 命令
3 CSV存储引擎
4 connect 存储引擎
mysqldump中的--tab选项
通过mysqldump命令,使用--tab选项,可以为每个dump的表生成两个文件。这两个文件的名字和原来的表名一样,不同之处在扩展名。其中一个以.sql结尾,其包含了创建一张空表所必要的create table命令。当然这个文件需要最先被执行除非目的端表已经存在。另一个是.txt文件。其包含了文本格式的备份。默认情况下是以制表符来分割每个值。并且使用换行符来分隔行。分隔符可以通过mysqldump的不同选项来按需指定。这些选项我们以后再说,因为它们需要使用很多工具和命令。
--tab 指定了 .sql 和 .txt 文件的路径。比如 --tab=/tmp/backup
通过 mysqlimport 导入dump文件
mysqlimport 是mysqldump的另一个延伸工具,它用来导入文本格式的备份。和mysqldump一样,所有的MariaDB发行版都内置该工具并且可以在bin目录中找到。语法如下: 因为mysqlimport需要知道表应该被还原到哪里,因此数据库的名字必须指定。然后,至少指定一个需要导入的文件。文件名即表名。扩展名到无所谓,可以是.csv .txt 或者是其他任何都行。这会有一个有趣的后果:两个文件名一样但是扩展名不同的文件会被认为是同一张表(??哪儿有趣)。比如对于大表,方便起见可能会把一个文件分割成多个。mysqlimport 命令有很多选项可以指定使用什么字符来分割行和列。这些选项我们在后面会说。
默认情况下,需要导入的文本文件多半位于服务器中。如果通过远程执行mysqlimport。导入的文件也可以在客户端,但是需要指定--local 选项。
如果需要的话,可以使用 --ignore-lines选项来跳过源文件的第一行,比如 --ignore-lines=1.当第一行是字段名,或者是一些相关注释(比如说时间戳或者软件产品信息等)的时候,这个选项非常有用。
另一个重要的选项是 --delete,使用该参数会在导入之前清空目标表。
当导入的目标端中已经存在文件中的记录时,可以使用--replace 选项来替换该记录,想要达到同样目的的,也可以使用 --ignore 选项来直接忽略已经存在的记录,可以防止报出一些错误(这点和上面的table-option类似)。
当导入大量数据时,也可以使用并行方式导入。 --use-threads 选项来指定多个线程来导入数据。比如如果我们使用 --use-threads=2选项,mysqldump则会使用两个线程。
通过select ... into outfile 来创建文本分割的文件
带有 into outfile子句的select 命令,可以把检索的结果集写入到文件。默认的,文件会存储到MariaDB的安装目录中(并不是数据文件夹)。当然也可以指定具体的导出路径。记住,MariaDB的用户必须对指定的路径具有文件级别的读写权限。在Linux系统中,/tmp目录是个不错的选择。注意,如果目标路径文件已经存在,会报错。因为此种方式只能把记录导出到本地服务器,因此使用客户端或者是远程都是不行的。
结果集不会输出到标准输出(客户端),但是会收到统计的记录数(或者是错误)
例子如下: 默认的,制表符用来分割字段,换行符来分隔行。(后面是几句废话,意思就是可以通过选项来改变分隔符)
select ... into outfile 命令是在两个服务器或者是MariaDB和其他软件之间交换数据最有力的工具。使用它来创建备份反而很少用。但是当我们仅备份表的一部分,比如使用join或者where子句的时候。就非常有用。
运行 select ... into dumpfile 命令等价于以下用法:
MySQL -e "select ..." > file_name
考虑下面这个例子:
MySQL -e "select * from information_schema.tables order by table_schema,table_name" > /tmp/tables.txt
当我们想要在shell中把查询结果转储到文件时,此语法非常有用。而且,它不仅可以在服务器端使用,客户端同样也可以。
通过Show create table 命令导出表定义
有时我们想获得表的创建语句。使用 show create table 命令是非常有效的,不信你看: 当然也可以对数据库使用类似的命令,比如: 使用以上两个命令。在数据库发生意外时,我们就可以通过 select ... into outfile 来还原数据。不过大多数情况下,它们还是多用在交换数据上面。
show create table 和 show create database 不支持把结果转储到文件。然而可以通过编写脚本来实现。
通过load data infile来加载dump文件
load data infile 算是select ... into outfile的反向工具,用来把导出的文件导入到已经存在的一张表中。
语法如下: 在语法描述中,设定分隔符的选项都在 other_options 中。巴拉巴拉巴拉。。一堆废话。随后再说。
low_priority 和 concurrent 仅用在非事物表中。low_priority 选项会比读操作的优先级更低。concurrent 选项允许myisam在当前进行插入操作。这两个选项都会减慢load的速度,但是不会阻塞当前的连接:如果指定了low_priority,其他会话中的查询可以被顺利执行。如果使用了concruuent,那么当前的插入操作也可以成功。
如果导入的文件需要由客户端发送到服务器端,那么必须指定local选项。否则假定文件在服务器本地。此时,MariaDB需要有文件相关的权限。另外不同之处是,当使用local选项时,如果出现主键冲突的错误,会把 error转变成warning并且不会中断整个导入过程。
replace 和ignore可以解决重复值的问题。replace会替换掉原有行。ignore不会动已有行,并且也不会抛出主键重复的错误。
INTO TABLE选项指定了目标表,可以是一个或多个目标分区。
//* character set 选项应该永远都使用,它表示文件使用的字符集。默认值是从character_set_database 变量中读取到的 *//
可以通过指定IGNORE n LINES 关键字来跳过文件的头几行。如果文件头头几行不是主要内容,比如说是列名的话。此参数很有用。
除非通过alter table 对表进行过更改,否则服务器会假定你需要导出的列的顺序是按照 create table 语句走的。除了通过create table 查看列顺序,通过 DESC命令也可以。
可以在括号之间明确指定顺序,方式与在INSERT语句中指定的相同
同时支持使用SET子句对多列进行计算。比如,如果 product表有一个price列;它还有一个sales_tax列,其值对应为price的10%. 由于MariaDB支持VIRTUAL和PERSISTENT计算列,通常无需使用load data infile 来插入已经计算好的值。但是在极个别的情况下也可能需要这么干。比如数据库是在多年前设计的,当时还没有这样的特性,并且我们也不想对它进行修改。 这可能是为了MySQL兼容性,或者因为计算值的表达式是不确定的,由于数据库不能用于VIRTUAL列。此时可以使用 SET语句来在导入之前先进行计算:
SET SALES_TAX =PRICE / 100*20
(是的你没看错,上面写的还是10%,下面他写成了20)
分隔符选项和子句
无论是mysqldump,mysqlimport 这样的命令行工具,还是select 。。into outfile 和 load data infile 这样的sql语句,他们都支持一些选项来指定字符,比如:分割值, 转义,换行等。
这些选项都一样。但是对于sql语句略有不同。
以下的表展示了具体的语法以及含义(此处不翻,突然变懒了。) 在SQL语句中,当使用多个字段或行子句时,不得重复使用 FIELDS和LINES。比如,正确地语法应该是 FIFLD TERMINATED BY ',' ESCAPED BY ‘|’ 。所有的sql子句都是可选的;如果它们出现,就必须以与上表中使用的相同的顺序出现。此处 COLUMNS和FIELDS同义。
创建和还原dump文件示例
很有必要讲讲如何创建和恢复dump文件。现在来看个简单的例子。展示一下如何使用select into outfile 来创建一个逻辑备份以及如何使用 load data infile 来进行还原。
先创建一张表来展示例子: 数据准备好了,现在使用mysqldump来导出数据,然后检查一下第一行是否正确。这张表非常小,因此也没必要仅检查第一行了。然而在真实情况下,最好还是检查一下。本例中,我们仍然会进行检查操作: 看上去还不错。现在再使用select .. into outfile 来对该表进行备份。此语句与之前的效果相同,区别就在于不会生成表定义文件。例子如下: 然后使用md5sum工具来检查一下导出的内容是否相等 由于md5值相等,因此可以判断两个文件也是相等的。
在还原操作执行之前,先清空表 现在先通过mysqlimport 进行还原: 随后再使用与之等效的 load data infile 语句。在进行下面的步骤之前同样需要先清空表。 通过CONNECT 和CSV引擎进行备份
使用CSV引擎创建的表为使用逗号分的隔数据文件,可用于备份或数据交换。CONNECT引擎就更加复杂了:它支持多种表类型。每种表类型都有不同的数据格式,支持的格式包括CSV,XML,HTML 和由dBase创建的数据文件。CONNECT引擎甚至可以通过ODBC连接到远程服务器来对远程节点进行读写操作。
通过CSV或者CONNECT来创建备份非常容易。例子如下: 但是没啥用。如果该表包含null值,CSV引擎就不能使用。这是非常重要的一个限制,但不仅仅于此。因此更倾向于使用CONNECT。使用CSV的唯一原因就是在mariadb10之前的版本不支持connect。也不能后期添加。
默认connect没有安装,因此需要通过以下命令进行安装。 现在我们可以使用它来进行备份了。把connect引擎的表类型指定为CSV,因为这种类型用来还原数据非常有效。当然也可以使用别的类型,但此处我就这么用还能咋地?注意下面例子中的表选项: 这个例子中,我们使用了CSV相关的所有选项。
1 table_type: 之前所说,设定源数据表的类型(此例中为CSV)
2 file_name : 表示数据文件的名称和可选的路径
3 HUGE: 这默认值为0.如果表大于2 GB,通过将CONNECT设置为1来通知CONNECT是有意义的。
4 COMPRESS : 由于这是备份,因此最好压缩一下, 与InnoDB一样,CONNECT使用zlib库和LZ77算法。
5 READONLY: 由于这是备份,所以使表只读是更安全的。
6 DATA_CHARSET : 使用的字符集。
7 SEP_CHAR: 列分隔符。
8 ENDING : 字符结尾的长度。 Unix系统为1(行以\ n结尾),Windows上为2(行以\ n \ r结尾)。
9 QUOTED : 因为指定了QCHAR,所以可以省略
10 QCHAR : 引用字符。
11 HEADER : 导出文件第一行是否是列名
(以上这几个含义翻译的实在牵强。查询翻译软件也无法获得更理想的解释。各位自己上手试试吧,母猴依稀)
还原同样非常简单:我们只需要删除/tmp 中的数据文件并且通过备份替换即可。通过CONNECT引擎,我们无需做更多的工作即可使用备份。然后,我们可以使用正常的INSERT ... SELECT或CREATE TABLE ... SELECT语句将备份内容复制到原始表中。
物理备份
物理备份是复制MariaDB的数据库定义,数据和索引文件,配置文件和日志。由于数据是以一种非常紧凑的方式存储的,物理备份通常是最方便的一种备份方式。同时也仅有物理备份可以备份配置文件和日志。
然而当进行物理备份时,必须对服务器进行加锁,也就是说在一段时间内服务器会进入不可用的状态。或者干脆停机进行备份。
何种文件需要进行备份
完整的备份应该包含以下几种文件:表文件,触发器文件,日志和配置文件。
表文件
表文件存放在data文件夹中。使用的存储引擎决定了数据和索引的文件类型。data文件夹中会为每个数据库创建一个目录。名字和数据库名字相同。所以最好别给数据库起什么奇奇怪怪的名字。表文件存放在对应的数据库文件下。文件名为表名。同样最好别起什么奇奇怪怪的名字。但是表文件的扩展名要看具体的文件类型。
如果是分区表,因为关联了多个数据和索引文件,这些文件的命名格式大致是这样的: .par文件用来存储分区的元数据。
data 文件夹的路径通过@@datadir变量进行设定。
对于innodb 的系统表空间,可以通过@@innodb_data_home_dir变量进行设定。其他表空间可以通过@@innodb_data_file_path进行单独指定。值可以配制成 @@innodb_data_home_dir的相对路径。
有些存储引擎,包括最近版本的innodb,支持为每张表指定不同的路径。路径可以通过DATA_DIRECTORY 和INDEX_DIRECTORY表选项指定。此选项可以通过show create table 命令进行查看。
掌握了以上知识,我们就可以对表或者分区进行选择性的备份了。
服务器创建的 .frm 文件包含了表的定义。一些存储引擎在某些情况下无需这些表定义文件仍旧可以工作。但是无论使用何种引擎,最好保证该文件存在。
innodb有一个file-per-table 模式会影响到表的创建行为。具体内容我们在第七章已经介绍过了。系统表空间总是存在,且以ibdata命名。当一个表通过 file-per-table 模式创建时,对于每张新表,都会有一个与之对应的包含了数据和索引的.ibd文件生成。当关闭file-per-table模式时,新的表将会被创建到系统表空间中。
许多存储引擎为索引和数据使用了不同的文件格式,下面是一些常见的扩展名: MERGE存储引擎不会创建数据和索引文件,但是它会使用一种MRG的文件,它是一张包含了与myisam表有关的清单。Aria也会使用日志,它们的名字是aria_log , 并且扩展名是渐进的数字,aria_log_control 文件也是必要的。archive引擎的索引支持非常有限,CSV干脆就根本不支持索引。因此这些存储引擎不使用索引文件。对于CONNECT引擎。SEP_INDEX表选项允许我们存储每张表的索引。具体的名字遵循 tablename_indexname.dnx的规则。 主键的索引名称为PRIMARY。
比如一张myisam1的myisam表使用了三个叫做 p0,p1,p2的分区。相关文件如下: 存储过程,触发器和事件,统称为存储程序。 它们旨在使用简单的SQL脚本实现数据库的逻辑。这些对象很少变动。然而备份这些程序对于还原来说仍然非常必要。它们的定义存放在系统表空间中,系统表空间包含在MySQL 数据库中。备份了MySQL库,就代表备份了所有已有的存储程序。
然而对于每个触发器,都可以在data目录中找到这样的文件
trigger_name.TRG
trigger_name.TRN
备份触发器的时候,一定别忘了这些文件。
日志文件
服务器日志的路径和文件名通过一些变量进行设定。这些已经在第二章和第三章讲解过了。此处汇总展示一下。 把日志文件放到data目录中会使得备份过程变更容易(事实经验告诉你,最好别这么干。备份都是自动的)
配置文件
(前面说了一大串if)简单来说,如果你一台机器上只跑一个MariaDB实例。那么对应的配置文件存放在MariaDB的安装目录中,Linux中叫做my.cnf windows下叫做 my.ini
Linux系统中,配置文件可以放在以下任一地方(太短了我就不翻译了):
windows中,路径可以是: 热物理备份
当服务器停机时,复制文件非常容易。但是当服务器运行时就有一个问题:必须确保在备份完成之前没有对文件进行过修改。
因此需要刷写最新的变更到磁盘中,然后锁住表。可以通过 FLUSH TABLE ... FOR EXPORT 或者是 FLUSH TABLES .. WITH READ LOCK 来完成。
语法如下: 使用 FOR EXPORT 时,table_list 为必填。with read lock 没这个要求。但是省略的话,flush tables with read lock 会锁住所有表。这称作全局锁。一个共享表锁会被加到已命名的表上。
//* 使用以上任一语句锁定的每个表都会从query cahce 中删除。因为服务器知道这些数据将会被替换,以后肯定是要从文件中读新的数据了。 *//
最方便的步骤如下:
1 打开MySQL客户端
2 执行 flush tables for export 后者flush tables with read lock 并让客户端处于打开状态。
3 复制相关数据文件。
4 客户端中通过 unlock tables 释放锁。
flush tables for export 会锁住表并且要求存储引擎刷写最近的变更到磁盘中。这是备份运行状态的innodb表的唯一安全方法。然而在MariaDB 10.0之后的版本,for export 子句就不可用了。同样其他存储引擎也不再支持了。
对于flush tables with read ,刷写操作由服务器完成,这就意味着即便存储引擎不支持这么写,但是通过服务器来完成也是OK的。然而如之前所说,这个方法对于备份运行状态的innodb表不大安全。
对于大多数存储引擎来说, 这两句并没有什么明显区别。但是如果备份innodb表,for export 就方便多了。
文件系统快照
一些文件系统或者卷管理器支持快照功能。比如 veritas这种的。其他文件系统,像XFS可以通过类似LVM的卷管理器来创建快照。快照是一种创建物理备份非常快速的方法。
无需停机即可创建快照。但是flush tables with read lock 来锁住表还是必要的。步骤如下:
1 打开客户端
2 执行 flush tables with read lock 保持客户端开启
3 进入系统控制动态,执行类似下面的命令 mount vxfs snapshot
4 回到客户端执行 unlock tables 释放锁。
大多数Linux发行版中都包含mylvmbackup 工具,可以自动完成上述过程。
通过rsync完成增量物理备份
rsync是一个Linux中用来增量复制文件的命令。当它对一个之前从未复制过的文件进行复制时,就直接复制它。但是当第二次复制该文件时,它会检查该文件自上一次复制以后是否被修改过。如果修改过,rsync会值复制修改过的部分,该方式通过网络进行复制非常迅速。当进行目录复制时,rsync会检查目录中的每个文件。如果源端目录被删除,那么rsync也会删除目标端的目录。然而对于备份来说最好别这样,如果文件丢失,我们还指望着备份来进行还原呢。
rsync命令对OLTP数据库没啥大用。然而对于OLAP这种包含海量数据而又很少变动的库非常合适。当备份这些数据库时。为节省时间考虑,只想备份已经被修改过的表。如果随后需要还原,使用对应表的最近备份即可。
以下是一个典型的rsync命令使用方法: 输出相当长,值截取了一部分。
可以使用以下选项
1 --progress :展示执行过程信息。输出非常长,但是出错的时候可以找到具体原因
2 --stats 打印传输文件的最终状态
3 --compress : 通过zlib进行压缩复制。想法不错,因为rsync通常用来复制大文件。但是应该让锁表时间尽量短。因此最好在释放锁以后再进行压缩,比如通过gzip或者其他工具。
4 -r 递归复制
5 -t 传输文件最近的变更信息。以方便下次增量备份使用。
6 -l 此选项遵循符号链接(如果有)。 我们通常不希望rsync删除目录中已经从源目录中删除的文件。 因此,我们没有使用--delete选项。
在服务器运行时进行文件复制
如果想还原备份,大多数存储引擎仅需要在复制备份文件到data目录之前通过 flush tables with read lock 锁住表即可。但是这在mariadb10之间的innodb表是不可行的。自mariadb10以来,innodb支持一种叫做 表空间传输(transportable tablespaces)的技术。意味着可以是使用一种特殊的sql语句来复制正在运行的服务器或者为正在运行的服务器还原相关文件。简单来说吧,这项技术可以在服务器运行时执行备份和还原操作。
此技术有些重要的限制
1 innodb 的file-per-table 必须开启。系统表空间不支持传输
2 如果foreign_key_check=ON,则不可复制。不过可以在复制之前设置改变量为OFF以关闭外键。如果文件随后被还原了,外键约束不会在还原过程中自动开启。
3 不同版本的MariaDB之间不可用。 只要服务器版本稳定,次要版本升级不应使备份表空间无效。
4 当在两个服务器之间复制时,目标端必须存在对应表。
为创建表空间的备份,需要执行以下几步
1 运行flush tables table_list for export ;为加上共享锁
2 为每个innodb表创建.cfg文件。之前我们没说。此文件仅为innodb表创建,并且也仅在当把表复制到运行状态的服务器时有用。
3 表现在处于一致和被锁的状态。设置 foreign_key_checks=OFF
4 复制 .ibd和.cfg 文件到备份目录中
5 设置 foreign_key_checks=ON
6 运行 unlock tables 释放锁
在运行时的服务器上还原备份:
1 运行 alter table table_name discard tablespace;为表加一个排他锁
2 复制 .ibd和.cfg 文件到data目录中
3 运行 alter atable table_name import tablespace;
使用 binary log 实施增量备份
binary log 是一系列记录数据变更情况的文件。它可以用作增量备份和复制。 binary log 的目的是能够将变更再次应用于数据库。对于复制环境,binary log有着更直观的表现:binary log 通过master 服务器发送到slave中,因此slave可以应用同样的变更以保持和master端的数据一致(并非立即生效)。如果不开启binary log的话,复制无效。
对于备份来说,binary log文件可以用来实施增量备份。当灾难发生后,数据可以通过最近的完整备份进行还原。可以是物理备份或者逻辑备份。因为数据有些过时,此时可以使用binary log。即等效最近的增量备份。因为其包含了所有的数据库变更操作。因此可以应用于完整备份。
可能举个例子会更清晰点。假如有个线上商店使用的MariaDB。每天数据都会改变:新的产品卖出买进啊,新的用户注册啊,网站流量数据统计啊等等。 我们显然需要经常备份数据。但是由于数据量很大并且网站流量非常高,因此不想减慢网站速度和使用大量磁盘空间来保存备份。这时可以每周选择一天网站流量低的时候进行一次完整备份。但是如果完整备份是在周日进行,然而周六晚上出事了,我们又不想丢失上一周的任何数据。可以在每天流量低的时候进行增量备份。最方便的方法就是循环使用binary log。循环日志也就是说新的日志被创建,当前使用的日志文件得以保留,或留在当前目录,或另行归档。为安全考虑,可以把这些文件备份放到远程存储服务器上。旧的日志留在原来的目录中对于还原操作非常有用。但是如果磁盘出问题呢?日志文件应该被存放在不止一块磁盘中。当新的完整备份创建后,这些保留整周的日志文件就可以放心地删除了。否则磁盘不够用咋办。(日志保留策略取决于公司的业务要求。极少情况下可能要求回档到很早以前。比如说消除证据。)
binary log可以以以下三种格式存储(我重复一下断句“可以,以,以下”)
基于语句 statement_based
基于行row-based
混合 mixed
对于statement-based 格式的日志来说。仅记录sql语句。这种格式有些限制:有些sql语句或者函数就不能用了,因为他们的执行结果可能每次都不一样。比如 NOW() 函数的结果取决于当前的时间。对于row-based 格式的日志来说,它会记录数据的变更值。mixed 日志有时安全,也有不安全的可能。选择一种可靠的格式对于复制影响非常重大。具体我们在第九章时候再说。
binary log也会在第九章中详细讲解。本章仅介绍一些binary log的重要概念。
可以通过 --log-bin启动选项或者 配置文件中的 log-bin 参数来开启binary log。无论选择哪种方式,其值都是具体的文件名,而并非 ON OFF这种。默认的名字是服务器的主机名后跟随-bin 字符。日志文件同样也会添加数字到扩展名中进行编号。
//* 如果开启binary log,@@log_bin变量为ON。@@log_bin_basename变量为当前日志文件的basename。 * //
show master status 命令可以展示当前日志文件的完整名称。如果没有使用复制环境,输出结果大致是这样的: show binary logs 命令可以展示当前的和旧的binary 日志文件 binary log 是以一种格式紧凑并且人和MariaDB都不可直接阅读的文件。如果想使用或者应用该文件,可以通过mysqlbinlog完成。此工具随MariaDB安装文件一起发布,它可以用来把日志文件转换成可读的sql语句。
mysqlbinlog可以用来把日志解析出来的sql语句发送到MySQL客户端。执行方法如下: 当然可以一次转换多个文件,跟上例一样,但是顺序必须正确。最好不要通过多次调用该命令来发送文件。 这是因为文件可能会尝试使用在另一个文件中创建的临时表。 如果我们单独的调用,临时表将在文件执行结束时丢失,并且不能用于下一个文件。
有时因为手残执行了诸如 delete,drop table或者drop database 之类的命令导致数据损毁。此时,我们想通过还原最近的完整备份和应用相关的binary log 来完成恢复。但是同样想避免二次执行相同语句而导致数据被毁。为此,可以把mysqlbinlog的输出写到文件中。手动编辑文件然后发送到数据库服务器,比如这样: 当然可以根据时间点来选择相应的事物。无论是开始还是结束时间都可以。比如这样: 此项技术可以避免执行了错误的语句导致重要数据被删除。可以通过检查mysqlbinlog的输出来确认需要执行的时间范围是否如我们所期望的那样。
对大多数情况来说,时间点并非是个精确值。因为时间戳没有精确到微妙这种级别,并且同一时间可能会发生很多数据变更。通常无需执行所有。幸运的是,每个条目在binary log中都有唯一的position值。为校验mysqlbinlog的输出,可以找出我们想开始执行的位置点。 它们作为注释和其他信息写在一起: 本例中,insert 的位置是694
除了指定 --start-datetime 和--stop-datetime 之外,还可以指定具体的位置点来进行更精确的筛选,比如这样: (--start-position 和 --stop-position) percona xtraBackup
percona xtrabackup 是一款来自 percona的物理备份工具。其最重要的特性就是可以在服务器运行时对innodb表进行无锁备份。至于使用其他存储引擎的表只需要在复制结束时锁那么一下下。percona xtrabackup支持增量备份。软件可以从 percona 的官网进行下载。
percona xtrabackup 由几个组件构成。核心组件就是 xtrabacup程序。 它内嵌了用于访问InnoDB表空间的InnoDB程序。除了备份innodb外也支持其他引擎。可以备份表定文件,触发器,以及通过调用 xtrabackup 来备份.ibd。percona xtrabackup 是一款复杂的工具。xbcrypt是一个独立的命令,用来对备份文件进行加解密。本书中,我们仅涉及如何使用 innobackupex来创建还原完整和增量备份。关于xtrabckup的更加详细地信息,大家可以从官网上阅读。此处提供相关地址:
http://www.percona.com/doc/percona-xtrabackup/
操作备份 percona xtrabackup 可以实现完整和部分备份。部分备份无需复制所有数据库的所有表。在第一次完整备份或者部分备份之后。percona xtrabackup就可以实现增量备份了。本章讲解如何实现所有的备份类型。
完整备份
完整备份无需多余的选项,下面的命令就可以简单地实现完整备份。
innobackupoex --user=root --password=root /tmp/backup
当然--user 和 --password 选项必须指定,其为mysql.user 中的用户密码。最后的路径表示备份将要存放的地方。
默认情况下,会在backup目录下创建一个子目录,其包含了当前的备份。目录的名字为当前的时间,格式大概是这样: 2014-4-10_16-30-18. 这通常是一种非常实用的归档备份方法。如果不想自动生成日期名字的话,只需要指明 --no-timestamp 选项即可。
部分备份
默认是完整备份。意思是它会包含所有数据库的所有表。当然也可以指定哪个库的哪个表必须进行备份。
--databases 选项用来指定需要备份的数据库,也可以指定表。多个值用空格隔开。每个参数可以是数据库或者表名。当只指定一个数据库时,该库下所有的表都会被备份。比如,如果想包含db_1的所有表和db_2的user表。大致语法如下: 如果需要指定的数据库和表太多的话,也可以写到文件中。innobackupex同样支持从文件中读取列表。每个表或库为一行。不过要使用 --tables-file 参数: --include 参数允许使用正则表达式来匹配表或库。比如这样: 准备备份
percona xtrabackup 每次拷贝InnoDB文件都无需加锁。也就是说。当表被复制时,用户可以修改表。但结果是复制的表很可能是不一致的。尝试还原一个不一致的表会损毁服务器。为保证备份一致性。需要执行prepared操作。prepare操作的在复制完成之后进行,并且无需连接到服务器和访问服务器中的任何文件。唯一的限制就是完成准备操作的xtrabackupex版本必须和复制操作的xtrabackupex版本一样。
innodb有一些用来重现或回档事物的日志。percona xtrabackup 会把这样的日志一起备份进来。在prepare的过程中,xtrabackup程序会对备份进行重做和回滚操作。因为程序内嵌了修改过的innodb程序,所以安全性是没问题的。在准备过程结束之后。所有的.ibd文件和日志都处于一致状态。此时如果有必要的话就可以直接导入到data目录中了。
准备完整备份
为prepare备份,我们需要应用所有的innodb日志。事实上,仅需要再次调用 innobackupex 且带有 --apply-logs选项即可: (不知道他为何举这个例子。innobackupex在哪?)
准备过程可能会受大的innodb表影响导致速度很慢。percona xtrabackup支持内存使用量参数来加速准备过程。默认的使用的内存为100MB,显然太小了。如果想加速这个过程。可以使用--use-memory 参数。比如: --use-memory=2G
如果prepare过程成功了。innobackupex 会在最后一行打印信息告诉我们最新的日志序列号。如果我们想要执行增量备份并且让 percona xtrabackup 无法访问到这部分备份,就需要把该序列号保存在别的地方。这种情况是有可能的。比如把备份复制到远程服务器然后从本地删除相应文件。
准备部分备份
Prepare一个部分备份的语法有些不同,看例子: 对于不包括在备份中的每个表,可能会出现一条警告,通知我们该表缺失。发生这种情况是因为 .frm 文件包含了这些表的定义。可以直接忽略。
对于包含的表, 会在backup 目录中创建 .exp 文件。该文件用来还原部分备份。
还原备份
如果想还原备份,可以直接把需要的文件复制到data目录中即可。然而 xtrabackupex提供了更简单的方法。
恢复完整备份
percona xtrabackup 不能在服务器运行时进行恢复操作。如果想恢复完整备份,需要先停掉服务器。然后调用 innobackupex --copy-back 命令并指定 backup目录。innobackupex会从配置文件中读取 data目录的地址。命令如下: 还原部分备份
可以恢复单独的表,正在运行的服务器也可以。使用方法本章已经讲解过了。(就这么短,真的)
备份的安全性
如果数据库包含敏感数据,说明备份同样包含敏感内容。这点千万别忘了。此处有几点来保证备份安全的经验。
1 设置正确地权限:只有许可的用户才能对备份文件进行读写
2 以安全方式传输备份。如果备份需要传送的远程服务器,传输必须是以安全的方式进行。比如通过SCP命令来使用SSH隧道进行复制。
3 加密备份:毕竟备份被偷走还是有可能的
4 备份存放到物理上安全的地方:物理安全指的是没有授权的人不能进去, 应配备防盗和防火装置。
如果有必要的话,可以使用防火墙或者selinux(个人认为防火墙应该由硬件来完成,使用IPS或者IDS。保证进入的内网的数据是干净的。重复使用防火墙没啥太大意义,还拖慢性能)
修复表
当一张表损坏后,有时无需使用备份来进行替换。它虽然也算个解决办法,但是说到底是会丢失一部分数据,除非表自从上次备份以来就没动过。因此最好先试试修复表。
修复步骤取决于使用何种存储引擎。
innodb 会在服务器启动时调用恢复进程自动完成。但是必须通过DBA来进行配置。
Myisam和Aria也可以在启动时自动恢复。除此之外也支持通过REPAIR TABLE的语句或者是当服务器停机时通过MariaDB中特定的工具。
其他存储引擎同样支持 REPAIR TABLE
//* 有些修复技术在安全性上考虑欠妥,但即便是最安全的方法,也有可能导致恢复失败甚至是数据错误。虽然这种可能性很小,但是还是有的。因此建议在修复表之前先对表进行一次物理备份。 *// 还原innodb 表
通常 innodb表无需修复。即便当表损坏,select ... into oufile 也可创建正确地数据。之前所说,此方法可以创建备份并用以还原。然而如果发生某种类型的损坏,select 会导致innodb崩溃。此时就不得不修复损坏的表或者直接通过备份还原了。
检查表
innodb 支持 check table 命令。如果表发生故障, 通常,该语句会返回一个结果集,其最后一行 Msg_type值为error,Msg_text列值为Corrupt。 在这种情况下,损坏的表或索引被标记为已损坏,并且直到问题解决之前都不能再使用。如果通过check table 检查出一些错误。 innodb 可能会立即停止服务器以阻止问题扩散。check table 在本章最后部分讲解。
事物日志
innodb 为实现事物使用了两种日志: redo 和 undo 。分别用来重现事物和取消未提交的事物。在崩溃恢复中都会用到。
在修改表数据之前,innodb需要把更改的内容写到redo 日志中。如果数据库在修改完毕之前崩溃。innodb仍然可以在重启阶段使用redo 日志中的信息重做整个事物。
redo 日志存储成单独地物理文件。为redo 文件提供了一个安全的buffer来减少对该文件的写操作。undo物理存放在系统表空间中,当然也可以配置为其他的表空间。如果磁盘是性能瓶颈,则配置这些物理存储的日志对性能优化非常重要。配置事物日志我们11章时候再说。
强制数据恢复
即使检测到数据损坏,innodb默认也不会执行数据恢复。只有在@@innodb_force_recovery设置为比0大的时候innodb 才会开始恢复进程。此变量非动态,因此必须在配置文件中对 innodb_force_recovery 参数进行设置。
只能使用大于0的值来恢复数据。恢复成功后,必须把这个值设置回0并重启。(有点设置成0就进入特殊的启动模式的感觉)
@@innodb_force_recovery命令决定了innodb采取何种行为来执行恢复操作。值越大功能越多,程 包含关系。值越小越安全。因此最好先尝试比较小的值。如果不行的话再尝试更高的。1-3基本安全。4可以导致二级索引失效。如果数据安全,二级索引还可以重建。5可能造成数据不一致。 6会导致页面处于过时状态。可能会导致这种错误被传播到别的地方。如果没有必要,最好别用。
当值大于3时,innodb拒绝所有的尝试修改数据的DML操作。当值为6时,innodb拒绝 CREATE TABLE和 DROP TABLE操作.
下面是对应值的详细解释
0 正常执行(不恢复)
1 尝试跳过冲突页,select语句不会导致innodb崩溃,冲突数据不会返回。
2 不启动master 和 purge线程
3 跳过回滚的事物
4 change buffer 不会合并页面。innodb表的数据不会进行统计
5 忽略undo日志。 对执行了一半的事务的不做回滚操作。
6 忽略redo日志。从mariadb10.0起,直到服务器重启,innodb一直处于只读状态。
当设置为6时,select中的where或者order by子句都会失败。简单的全量查询可以帮助我们创建一张备份表。然而当发生内部页错误时可能会出错。如果查询在某一点失败。仍可以尝试使用where 或者order 来跳过错误页。
修复非innodb表 修复表的过程取决于存储引擎 。不过大多数的存储引擎都支持通过sql语句来检查文件完整性和执行修复操作。myisam和aria支持其他的恢复方法。sql语句允许我们可以通过以下两种方式处理数据错误
1 check table语句来检查表是否出错
2 repair table 语句来修复出错的数据
myisam 和aria同样支持
1 启动时的自动回复
2 myisamchk和aria_chk 工具来修复对应引擎的表
check table 语句
当运行sql语句时,可能会收到一个当前我们正使用的表被损坏的信息。比如这样 在其他情况下,MariaDB不会意识到表已损坏, 我们可能会意识到是因为我们收到不完整的结果集。最坏的情况就是服务器崩溃。如果我们定期检查错误日。就会注意到这点。 在崩溃之前,可能会看到InnoDB错误,通知我们表已损坏。
如果我们认为表出错,但是又不太肯定。那么check table可以让我们无需停止服务器来检查表的完整性。以下几种引擎都支持check table语句: innodb,myisam,aria,archive,csv。 检查的语法如下: 当检查分区表时,下面的语句可以让我们仅检查一个分区: Table_name 和partition_list如有多个用逗号隔开。可以指定0或多个选项。选项参数及释义如下:
1 for upgrade:当需要导入的表文件是通过更老版本的MariaDB创建的时非常有用。在把数据导入新版本的mairadb服务器时,应该调用mysql_upgrade脚本对文件进行预处理。当然此选项也可以用来升级表文件。
2 changed: 只检查自上次CHECK TABLE命令以来修改过的表。最近一次更新和检查时间可以通过 information_schema.tables表进行查看。只对myisam和aria表起作用
3 fast: 此选项检查表是否已正确关闭,以及表或索引是否标记为已损坏。在服务器崩溃之后,此选项提供了充足的信息用来判断表是否损坏。只用于myisam和aria表
4 quick:不检查删除链。检查删除链需要花费很长时间, 但此选项可检测大多数类型的损坏。只用于myisam和aria表。当使用change选项时,此选项默认启用 (附上delete link的官文:In MyISAM tables, deleted rows are maintained in a linked list and subsequent INSERT operations reuse old row positions. 删除的行的位置会写进一个链表中,以供后续插入的新行使用。大概是这个意思)
5 medium: 通过校验和对比来检查数据和索引的完整性。 很少遇到此选项检测不到的异常。为默认启用
6 extended: 此选项对表执行完整性检查。 如果表很大需要花费很长时间。
如果 check table 不能检测到任何错误。则返回类似下面这行,比如这样: 如果CHECK TABLE无法指出错误,返回的结果集可能是这样:(原文是: If CHECK TABLE cannot correct the problems,因为check table 不是用来修复的,所以此处correct翻译为 指出错误 而非修正。我是这么觉得) 我们需要注意msg_type 和msg_text内容。
如前所述,当使用CHECK TABLE检测到nnoDB表损坏后, 它试图使服务器崩溃,以立即停止异常传播。 如果发生这种情况,我们需要强制InnoDB在启动时尝试修复损坏的表。
repair table 语句
如果表损坏了。我们无需停机即可尝试修复。repair table语句就是用来完成这项任务的。该语句支持以下几种引擎: myisam,aria,archive和CSV
//* innodb 表不支持该命令 *//
语法如下: 和check table 类似。同样适用于分区表,巧了!连语法也很像: 注意,后者不支持 USE_FRM QUICK选项仅用来修复index文件。由于索引比数据文件更易损坏,故此选项挺有用。
EXTENDED ,索引以一种缓慢但是安全的方式来重建。但是 可能会生成许多垃圾行。因此,尽量别用这个选项。
如果myisam表的索引文件 .myi丢失,则可以使用USE_FRM选项。索引文件丢失很有可能,比如repair table 语句修复失败导致服务器崩溃并留下空的索引文件。虽然很小概率才会发生。但是如前所述,我们应该始终对损坏的表进行备份,然后再尝试修复它们。
注意一点,对于USE_FRM来说,delete link也会被删除,也就是说 将无法使用OPTIMIZE TABLE语句回收未使用的空间。 此外,表的AUTO_INCREMENT值(如果有)将丢失。因此如果能选别的就选别的,尽量别用USE_FRM。 此外,它绝对不要用于压缩表,因为会对压缩表造成损坏。可以使用myisamchk或者aria_chk这样的命令。
对于 NO_WRITE_TO_BINLOG选项,则不会写入binary log。这在复制环境中想要避免把变更复制到slaves端时非常有用。此处使用LOCAL与NO_WRITE_TO_BINLOG意思相同。
修复CSV表
CSV引擎支持 check table 和 repair table语句。然而repair table 对csv引擎有着重要的限制:修复时遇到损坏行立即停止。
比如1000行的表,第十行损坏了. repair table仅能恢复前9行。不过我们可以备份一下该表,然后用文本编辑器打开,删除第十行,重复修复过程直到所有没损坏的行都被还原。但是这个方法不仅不安全, 还慢,更重要的是容易烦躁。
使用 myisamchk 和 aria_chk 修复表
所有的MariaDB发行版都包含这两个工具。它们可以用来检查或修复myisam或aria表。使用myisamchk的时候要求不能有其他程序访问表。换句话说,在运行myisamchk之前,需要锁住相关表或者停止服务器。
//* myisamchk 和aria_chk工具不能应用于分区表 *//
语法如下: table_list参数可以指定为以下几种方式
1 表名
2 表对应的索引文件名,表中的数据仍然会被检查
3 可以使用通配符匹配
通过强大的通配符来指定表名,比如下例表示选择了 joomla数据库中的所有myisam表 如果想指定所有数据库中的所有myisam表,可以这样: 如果想指定db_1中的 customer和user表,可以这样: //* 除非被检查的表处于和myisamchk或者aria_chk的同一目录,否则必须指明具体路径。这些工具可不知道MariaDB的目录树结构*/
下面列出了几个myisamchk和aria_chk的选项,至于其他选项,我们通过表格列出
1 --check:检查被选中的表是否出错。默认选项
2 --force:包含--check动作,如果出错,则尝试修复表
3 --repair:如果表出错则尝试修复。
4 --analyze:分析索引,类似 analyze table
下面的表格展示了myisamchk和aria_chk的其他用来检查表的重要选项(此处不译) 当修复表时,可以使用下列选项(同不译) aria使用日志文件和日志控制文件来来修复损坏的表。aria_chk有几个特定的选项用来指明如何使用这些文件来修复表(不译) myisam 和 aria的自动恢复
myisam和aria支持自动恢复功能。当服务器开启此功能时,服务器会检查表是否被标记为损坏或者而由于服务器崩溃导致表 没有正确关闭。如果检测到,服务器会检查表。如果检查到错误,会尝试修复问题。当aria的自动恢复功能打开时,只要aria表被打开,此套检查动作就会执行。
由于系统表空间在MySQL数据库中并且为myisam引擎控制,开启myisam的自动恢复非常重要。所有生产环境服务器都应该开启此功能。 如果表使用FIXED行格式,修复MyISAM表成功的可能性更大。因为这种格式下,myisam总能知道行从哪开始,从哪结束。
《MariaDB knowledge base》中对aria的称呼为:崩溃但安全的引擎。此处并非说aria表不会出错,意思是修改aria表的每条语句都具有原子性。要么完全成功,要么完全失败。
myisam恢复可以通过myisam_recover_options 选项来配置。可以配置为如下值,可以使用多个,用逗号隔开即可:
1 off:关闭自动恢复
2 quick:进行快速检查
3 force:最好但是比较慢的检查
4 backup: 保留data文件的备份
5 backup_all:保留data和index文件的备份
6 default:同OFF
对于aria表的自动恢复来说,可以通过 aria_recover选项进行调整,其可选值如下:
1 OFF:关闭自动恢复,默认值
2 QUCIK: 进行快速检查
3 NORMAL:一般检查
4 FORCE:检查更狠点
5 BACKUP:保留data文件的备份
最安全的配置案例如下: 总结:
本章我们讨论了各种备份技术和工具。dba应该对这些备份方法了如指掌。因为每种方法都能在不同的环境下使用。
我们讨论了如何完成一个物理或逻辑完整备份。当有一个完整备份的时候,还可以执行增量备份,它比完整备份更小更省时。随后我们讨论和如何进行部分备份也就是不会包括所有的数据库的所有表。至此所有的备份类型算是说完了。
在MariaDB 10之前备份 innodb表会很麻烦,需要服务器停机。我们讨论了如何备份和恢复innodb表。着重介绍了 percona xtrabackup工具。它专为innodb做了优化,备份更快且无需停机。
当表损坏时,通过备份还原并非唯一解决方案。我们讨论了如何修复表。操作步骤取决于具体的引擎。innodb恢复进程在服务器启动过程中调用。有些存储引擎支持sql语句进行修复。还介绍了用来修复 myisam和aria表的两个特殊工具。
下章中,我们将会讨论如何安装和管理复制环境。 第九章 复制
MariaDB支持复制技术,复制技术用途广泛,但最常用的就是构建复制环境以增加数据冗余度。复制无法替代备份策略。如果让slave端作为master端的备份有可能会导致数据丢失。复制的另一处用途就是读写分离。
上一章我们介绍了备份。了解到这一主题的重要性。复制也算是备份的一种吧。它基于binary log。
本章我们讨论一下几个主题
1 MariaDB中的复制如何工作
2 配置master 和 slave
3 加载数据到slave或master
4 配置master和slave
5 循环复制日志
6 检查slave端数据完整性
7 解决复制环境中的常见问题
复制概念
MariaDB支持内建的复制技术,此特性是一个非常古老但是又十分优秀的技术。第一版是在MySQL 3.23.15中出现。当时MySQL甚至没有innodb,也不支持union和视图。当然第一版的复制已经非常落后了。但是master端记录sql语句到日志然后把日志发送给slave端执行这一基本方法没变。如今复制环境变得原来越稳定强大了。
MariaDB复制技术基于binary log。binary log会记录下所有修改数据库的操作。binary log支持以下三种格式
1 语句
2 行
3 混合
对于 语句格式,条目由所有修改数据的sql语句构成。对于行格式,条目由所有由于语句修改产生的结果构成的(类似变更向量?)混合格式记录语句格式,但是如果有必要也会记录成行格式。取决于格式,我们可以把复制定义为基于行的和基于语句的。binary log在本章会详细介绍。
MariaDB的内建复制技术叫做异步复制。也就是说master和slave之间的连接无需一直保持。
//* 很可能随时停止复制来获得一个master端的快照。比如,如果想进行一个快速备份。当slave端再次启动时,它会接收到master发来的复制环境停止那段时间产生的所有条目。对于slave端崩溃是一样的 *//
客户端可以通过slave端进行查询数据。读负载重的系统可以使用此特性。比如可以通过把多个slave连接到同一master中来分散查询请求。
每个slave都有一个master。比如服务器A可以是服务器B的master,服务器B也可以是服务器C的master。如果B挂了,BC之间的复制会暂时停止,然而如果B丢失了数据,可以通过C来作为一个备份。当然在我们这个例子中,C也可以是A的master。这样把复制环境搞成一个圈叫做 环形复制。它可以在任意节点修改读取数据并保证数据一致性。因为每次修改都会通过环形结构复制到环中的每个节点。 主要缺点是没有一个服务器能始终包含最新版本的数据。
MariaDB 10 支持多源复制。此特性允许slave端从多个master端复制数据。但是由于MariaDB中没有冲突控制,因此多个master必须传送不同的数据。也就是说最好不要从多个master复制同一数据库。多源复制允许你使用一台机器或者有限的机器来从多个master中复制数据。 这种技术可以降低硬件的成本。(我测试了一下,多源复制同一张表没问题,但是千万不能有主键冲突,如果有冲突,必然挂)
在复制环境中,最好所有的master和slave都使用相同的MariaDB版本。从老版本的master复制到新版本的slave中不可行。比如 5.5的master不能复制到 10.0中。从新版本的master复制到老版本的slave中通常没问题,但是仍旧会导致一些问题。MySQL服务器可以存在于复制拓扑中。
复制如何工作
本节我们讲解一下MariaDB中的复制是如何进行的。 尤其是关于读取操作将会使用哪些线程以及保留哪些日志。这些知识对于以后的章节非常有用,包括如何配置master和slave以及如何管理复环境。
复制线程
在MariaDB 复制中,使用了三种复制线程,如下表: master和slave之间的连接由slave请求发起。当slave启动时,它会创建SQL I/O线程,随即连接到master申请获得必要的条目进行复制。
在master中会启动binlog dump线程。此线程用来接收来自slave的SQL I/O 请求并发送其所需要的binary log 条目。在 show slave status 命令的输出结果中,此线程叫做 slave_io_running. show processlist的输出中叫做 binlog dump.
SQL I/O线程不直接执行任何条目。它仅把条目写入一个叫做 slave relay 的日志文件中。
slave sql 线程从relay 日志中读取并执行日志条目。
并行复制
在MariaDB 10 之前,每个slave仅有一个 slave sql线程。因为正常情况下master执行写操作会使用多个并行线程,仅有一个slave sql线程有时并不能满足复制环境下的性能要求。 在MariaDB 10中,推出了一项叫做 并行复制的特性。oracle在MySQL 5.6中引入类似的功能。然而MySQL的用户需要注意MariaDB和MySQL使用了不同的并行复制方法,并通过不同的方法配置。比如MariaDB中最重要的并行复制变量为 @@slave_parallel_threads 。MySQL中并没有。MySQL中的并行复制线程叫做 --slave-parallel-workers.在MariaDB 中并没有。
并行复制由一个线程池构成,它可以以并行方式应用日志条目。每个线程池中的线程叫做worker线程。到那时记住,并非所有的条目都可以被并行应用。MariaDB 仍然会顺序执行某些条目,这对于复制的正确性很有必要。
此特性可选并且默认并未开启。如果想使用的话可以在master上配置,通过@@slave_parallel_threads 服务器变量即可。此变量指明了slave启动时的worker线程数。会导致所有的slave都以同样的worker线程数来从同一master中复制数据。当然,如果一个slave要从多个master上复制数据,那么所有的master上配置的@@slave_parallel_threads 变量必须相同。
slave log
slave 端 需要记录有关复制配置和当前进度的信息。这些信息一定不能丢失,即便发生崩溃。到此总结一下,每个slave包含三种log
1 relay 日志包含了从master端传来的binary log。如之前所说,此日志由 salve i/0 线程写入,并由 slave sql 线程读取。如果使用并行复制的话,则由worker线程读取。
2 master 日志 存储了连接到master端的必要信息 以及master的binary log的坐标,此坐标由日志文件名和接收到的最新的binary log位置构成。
3 relay 日志的日志。记录了最新的relay 日志条目的应用情况。
relay 日志总是写入文件中。master 日志信息和relay 日志的日志信息可以写入文件或者MySQL数据库的系统表中。
即便在多源复制中,每个slave的每种日志都仅有一个。 选择binary log格式
选择binary log 格式是非常重要的。如果使用语句格式的,开发人员需要注意一些重要的限制。
可以通过 binlog_format 变量来指定binary log的格式。如果不指定,默认为语句格式。可以通过 @@binlog_format 变量来查看当前使用的格式。
//* @@binlog_format 为全局和会话级的动态变量。也就是说可以在服务器运行时修改,无论是对所有连接或者是当前链接。然而最好别这么干,在master 上修改binary log的格式可能会导致复制中断或者其他一些意外。因此修改 @@binlog_format 需要 SUPER权限。但是在slave中修改日志格式没问题。
不能在存储程序中修改 @@Binlog_format 格式。master和slave 没有强制必须使用同一种binlog 格式。*//
基于语句的binary log
基于语句的binary log格式是最传统的方式。当使用此方式时,日志会记录修改数据的语句。比如像 update, delete或者带有where子句但有时并不修改任何行的replace都会被写入log中。但是像select这样不修改数据的就不会写入日志了。
statement 格式有一重要限制:只有确定性的语句才应该被发送到服务器。确定性意思就是如果在相同的服务器中执行两次,结果应该是相同的。 它们必然具有相同的效果。 当然这个限制对于不修改数据的语句无效,比如之前说的select,它既不会写入日志,也不会影响到复制数据。如果一条语句使用了当前的时间戳,当前的用户或者随机数,那么就可能在slave端产生不同的结果。
如果使用statement 格式,MariaDB会检测那些不安全的语句并抛出警告: 但是,MariaDB不会检查使用的用户变量的值是否为确定性。因此,当使用变量时,很容易出现插入一个非确定性的数据并且不产生任何警告的情况。 如果使用了存储程序,尤其需要注意这些地方。
另一方面, MariaDB用于检测非确定性语句的启发式技术不大好使。换句话说,由于这里使用的算法没能正确分析一些语句,导致一些确定性的语句也会抛出1592警告。此著名的bug可以导致error log飞速增长。某些情况下,还是使用MIXED格式会比较好点。
此处列出什么语句会导致结果为非确定的。
1 非确定性函数,比如RAND(),USER().FOUND_ROWS(),ROW_COUNT(),LOAD_FILE() 都被认为是非确定性函数
2 用户定义函数(由C语言编写并安装到服务器中)
3 使用服务器变量。这些值可能在slave端并不相同。也 有一些例外,但由于目前没有记录,我们必须依靠自己的经验和逻辑去判断。
4 带有LIMIT子句的update(即便指定了order by也不行。算是一个bug吧)
5 AUTO_INCREMENT 列被修改, 并且该语句会导致触发器被执行或者调用存储的函数。
6 AUTO_INCREMENT 值自动生成,并且并不是作为主键出现。
7 在10.0版本之前认为LOAD DATA INFILE也是不安全的
8 INSERT DELAYED会与myisam表冲突。(其他引擎会忽略DELAYED子句)
9 语句中使用了系统表。这些表在mysql,information_schema,performance_schema库中。在不同的服务器中它们的值是不同的。即便服务器接收的是相同的语句。 仅有read committed 和read uncommitted 可以用statement格式。理由就是当使用 repeateable read 和serializable时。语句的执行顺序会取决于语句的执行时间。比如因为有些语句会使用用户锁之类而使得语句执行的时间更长。
当然并不是所有的非确定函数都是不安全的。 原因是有些函数产生的binary log日志包含了准确复制的信息。比如date或者time函数,由于binary log为每条语句存储了时间戳,所以它们是安全的。比如这些都是安全的非确定函数: //* sysdate() 函数是时间函数中唯一一个不安全的。因为它返回的是当前函数调用的时间,并非语句执行的时间。同一个查询中的多个sysdate()函数调用会返回不同的结果。 因此,不可能复制它获得相同的返回值。不过这些限制只针对于 基于语句的binary log。但是,即便不适用复制环境,binary log中也不要包含非确定语句。 毕竟备份还是要用binary log的。 *//
基于行的binary log记录
如之前所说,如果使用ROW格式,则binary log包含了数据的变更情况。但是有些例外:有些操作会以不同的方式写入日志,比如:
1 ddl语句,比如 create table 和drop table
2 隐含地修改MySQL数据库中的表的语句。比如像 create user 或者grant 这种。 显式修改系统表的语句,如INSERT或UPDATE例外。
3 针对临时表发起的语句。
以上这些例外会以语句方式记录在日志中,如同使用statement格式。比如,create ...select 语句会以不同的方式记录:create table 部分会被写成语句并以语句方式记录,select涉及数据的部分则会以ROW格式记录。
ROW格式的主要优点是它没有STATEMENT格式的限制:ROW格式中所有语句都是安全的。只要数据修改被记录,语句产生的结果是不是具有确定性跟数据变更情况毫无关系。任何隔离级别都可以使用ROW格式,包括 read committed和read uncommitted。
当然,slaves端无需再次执行SQL语句。执行语句会涉及一些额外的动作,比如对行进行分组或排序或执行某些函数等等。应用ROW格式的变更对于slave端则会更直接,操作也更少。如果master端执行的语句非常复杂,则slave端可以从此项特性中获益。
当然也有不利之处:如果一条语句修改了许多行,row格式需要在log中写入大量的数据变更。这会使得binary log变得非常巨大。如果使用复制环境,master端会发送更多的数据到slave端中。
然而,row格式比数据本身更加紧凑简洁。通常写入数据库的数据都是以insert或者update方式进行的。如果要通过这种方式插入大量数据。statement格式会使得binary log变得异常巨大。当然,如果是多行语句仅对一行语句修改,则statement格式对负载没什么影响。
MIXED binary log格式 混合二进制格式
statement格式有许多限制,row格式有时又不利于工作负载。因此可以使用MIXED格式。大多数情况下,使用MIXED格式意味着绝大多数语句都会被记录成STATEMENT格式。当检测到非确定性语句时,则会使用ROW格式记录。以MIXED格式记录的二进制数据叫做 binary injection.(二进制注入?)注意,只有read committed 和read uncommitted隔离级别可以使用statement或者mixed格式。因为对于repeatable read格式来说, 锁定事务的持续时间可能会影响其他语句的执行顺序。 这可能会导致master和slave之间出现差异。
存储程序中的二进制日志记录。
存储程序 (过程和函数)只有在确定性且不修改任何数据时才是安全的。因此当开启binary log时,如果没有以NOT DETERMINISTIC,READS SQL DATA,CONTAINS SQL或者NO SQL语句声明,MariaDB会阻止你创建存储程序。如果想强制创建,可以通过设置 @@log_bin_trust_function_creators 来完成。然而这样记录的语句在statement格式下并不安全。
对于触发器来说,不能使用 deterministic 或者not deterministic来声名,不过如果它使用了非确定语句,则同样不安全。
另外,我们必须记住,如果权限不同,SQL语句的行为会有所不同。slave端使用拥有所有权限的用户来执行所有的语句。 如果我们不确定存储程序总是有权执行他们在master上尝试的所有操作,我们就不该以statement格式记录。
配置复制
本节我们来研究一下复制环境中master和slave上都需要配置什么参数。
以下所有示例同样可用在多源复制环境。当然还是有些差异的,我们放在后面再说。
配置复制环境至少需要以下几步:
1 配置一个新的master
2 配置一个或多个slave
3 从master中加载数据
4 启动slave
5 检查slave运行情况
我们下面逐个讲解
配置一个新的master
当安装复制环境时,首先要做的当然是确定至少一个master。其实就是选一台有唯一id和开启了binary log的MariaDB服务器即可。
首先需要给各个节点分配server ID。 server ID要求在复制环境中是唯一的。 它必须是4个字节的整数值,最小值为1。如果没有配置server ID或者设置为0,表示关闭复制功能。
如之前所说,master中必须开启binary log,它用来记录master上的数据变更以发送给slave再现事物。
master的配置文件需要包含至少一下几行: @@server_id 和@@binlog_format为动态,可在服务器运行时调整 @@log_bin 变量并非动态,更改此变量需要重启服务器。
//* 最好把变更写入配置文件,以备下次服务器重启时可以自动读取,如有特殊情况仅临时调整的除外。 *//
然后,我们需要一个账户用以复制工作, 严格来讲,slave端的账户只需要 REPLICATION SLAVE 权限就够。如果有些数据库一定不能复制给它们,则不能授予该权限(???)。可以给所有的slave端节点使用同样的用户。 他们甚至可以使用与其他客户端共享一个帐号。然而这样就需要把密码存放在一个叫做 master.info 的文本文件中。最好给它们设置不同的密码。当然,为了提升数据安全性,每个slave端用户应该仅能连接到指定的主机。最好能通过SSL来进行连接。
下例展示了如何创建一个安全的复制账户 请注意,REPLICATION SLAVE权限不要与REPLICATION CLIENT 权限混淆。该权限应该授予那些需要进行复制配置,诊断的用户,因为该权限会许可用户执行 show master status 和show slave status 命令。 生产环境中,除非真的需要,否则尽量不要用root账户。
关于安全和账户管理问题,可以参考第五章。
配置一个新的 slave 端
在配置了一个或多个 master之后,现在需要配置slave了。此步骤为配置复制环境必须的,即每次添加新的slave节点都要这么做。
和master很像,每个slave都需要一个唯一的server ID。
不过没有必要开启binary log。除非该slave扮演着其他节点的master。如果不是这种情况,开启binary log对于备份当然也有用。但是会影响性能。
下例展示了slave中需要的最少配置:仅一个server ID就够了
server-id =2
slave可能会扮演其他一个或多个节点的master。这时,slave就需要开启binary log来记录从master上接收到的变更条目。 以便其slave能够检索它们。默认情况下复制条目不会被记录。如果想复制它们,我们必须开启 binary log以及设置 @@LOG_SLAVE_UPDATES=ON。此变量非动态,更改需要重启服务器。
下例展示slave扮演其他节点的master的最少配置: 启动slave
如果master上没有数据。那么配置复制就非常简单。在填写了上述配置参数之后。按照以下几步执行即可:
1 在master上添加全局锁。必须确保在启动slave之前master是不能工作的。
2 获取binary log的热状态。包括binary log的名字和最新的position(日志的位置,类似oracle中的scn)
3 释放master上的锁
4 为slave提供连接到master的必要的信息。我们将通过change master命令来完成
5 在多个slave上重复操作。
下例展示了如何获取master的binary log状态。 如上显示,master使用的binary log为 binlog.000031 ,并且当前位置为 323.slave 将会从该文件的该位置进行复制。
change master to 语句可以用来告诉slave连接到哪个master进行复制。基本语法如下: 此时,slave已经获悉了连接到master上进行复制的所有信息,必要的权限在master上已经配置过了。一切准备就绪,现在只需要执行 start slave 命令即可。 这些连接参数以后可以随时更改,但是更改以后,必须临时重启一下复制。 当然,如果服务器重启了,slave也会跟着重启的。
检查 slave 是否运行
可以通过 show slave status 命令来查看slave 线程是否运行,比如: 输出列非常多,此处截取一部分,只保留用来确认slave处在运行状态的几个重要参数。 slave_io_running 和 slave_sql_running 列必须为yes。如果出现其他值,slave_io_state和last_error可以帮助我们找出问题所在。比如如果连接出错,则可以看到类似一下错误。 重新配置已有的slave
有时我们想对以后的slave重新配置。不管是想把原有的数据库提升为master,还是过滤规则设置的不正确,不管怎么说把,就是想让slave从现在起忘掉之前复制的数据,重新开始。
在重启之后,slave需要复制自master启动以来的 binary log(这么说是因为在当前两端都没有数据的大条件下,但是真实情况手段很多,没必要用这种追溯历史的方法,除非及其特殊情况,当然我想不出来)。因此必须抹除当前的状态。reset slave 命令可以通过删除当前的slave日志文件来抹除这些关联。操作此命令必须停止复制。
比如这样: 有时我们需要用新机器来替换现有的master。不管是现有的master太慢还是硬件坏了。不管怎么说我们都需要从旧master上把数据迁移到新master中。
我们可以选择在slave已经运行并且连接到master时做迁移。第八章已经讨论了如何使用mysqldump来导出导入数据。dump过程会自动复制到slave端。
除使用逻辑备份外还可以通过复制文件来完成。此时,如果slave正在运行,备份是不会自动地被复制的。因此需要我们把物理备份复制到slave的data目录中。
从master上导入数据到slave中
复制环境并不是静态的。很有可能随时添加slave节点以通过大量MariaDB服务器的方式来解决数据冗余或负载均衡等问题。这种情况下,我们需要把master的当前数据导入到slave中,然后slave才可以进行复制。
可以通过创建物理备份然后拷贝至slave的data目录中来完成。常规的物理备份和用于复制的物理备份没有任何区别。
当然也可以使用mysqldump。这个工具已经讨论过了,但是它还有一些参数可以使得把数据还原至slave的过程更加容易。dump过程可以从master或者slave发起。latter选项可以避免master负载过重。
从master中导出数据
当从master上导出数据时可以使用 --master-data选项。此选项会添加 change master to 语句到dump文件中,因此slave就可以自动配置复制参数。
--delete-master-logs选项可以在master上执行 purge binary logs命令,该命令用来删除旧的binary log文件,本章随后会讲到。
从slave中导出数据
现在我们想使用--dump-slave这个参数,它和 --master-data非常相似,但是在 change master to 过程略有不同。如果使用 --master-data,mysqldump会假定当前节点为复制环境中的master,而--dump-slave则会假定当前节点为slave,该slave使用的复制参数将会填写到 change master to 语句中。注意此区别非常重要。(其实就是从哪个角度看,一个认为本机为master,一个是把当前使用的master写到dump文件中)
不过需要注意,当使用 --dump-slave时虽然在dump文件中会包含复制状态,但是不会包含master的主机名和端口,因此还需要指定 --include-master-host-port 选项,以确保在change master to 命令中添加 master_host 和master_port 子句。此选项使得配置slave更加容易,除非dump从其他slave中导出,或者不知道为啥master的主机名和端口号都变了。
通过 --apply-slave-statements 选项可以使得dump文件中在change master to 命令前后添加stop slave 和start slave命令。此参数在slave正在运行时非常有用,如果slave没有运行,那么start slave会使得复制立即开始,我们最好先在slave启动之前测试一下数据一致性,别让它立即运行。
过滤binary log 条目
有时需要避免复制一些内容。可以在master或单独地slave中完成。(就这么一句话。)
SET SQL_LOG_BIN 语句
SET SQL_LOG_BIN 语句可以用来在会话级开启或关闭 binary log的记录行为。仅影响当前会话。如果设置为0,则之后的语句将不会写入binary log,也不会被slave进行复制。示例如下: @@skip_replication 变量
@@skip_replication 变量和 set SQL_LOG_BN 非常相似,然而在内部的日志记录上有些不同。它使得那些跳过复制的语句被打上skip_replication 的标记。slave仍旧会接收到这些条目,不过具体的行为取决于 @@replicate_events_marked_for_skip变量。
1 REPLICATE :仍旧被复制,标记被忽略,此为默认行为
2 FILTER_ON_SLAVE: slave 会忽略这些条目,不过仍旧会接收并记录它们,如果slave开启了binary log的话,相应的标记也会添加到binary log中去。
3 FILTER_ON_MASTER: slave不会接收这些打了标记的条目。
从slave中过滤复制条目
slave中有三个动态变量可以用来阻止表或库进行复制。多个表或库名通过逗号隔开。在改变这些变量之前,必须停止复制,三个变量如下:
1 @@replicate_skip_db:指定的数据库不会被复制
2 @@replicate_skip_table 指定的表不会被复制,表名需要写成 db_name.table_name的样式。
3 @@replicate_wild_skip_tables: 和上个变量类似,不过它支持通配符。比如test%.% 则会阻止来自任何库的任何以test开头的表进行复制。
当然也可以反过来配置,比如不允许复制所有表和库,但是除了以下几个参数指定的除外:
@@replicate_do_db
@@replicate_do_table
@@replicate_wild_do_table
校验 binary log 条目
从 MariaDB 5.3 开始,可以在每个复制条目中添加校验信息。此特性默认关闭,因为它会修改binary log的格式,增加不兼容性。为添加校验信息,可以设置 @@binlog_checksum =1 。开启此选项会使复制更可靠,但是会对性能造成影响。
可以在几种情况下验证校验信息
1 如果 @@master_verify_checksum=1 ,则slave i/o线程会对接收到的条目进行校验
2 如果@@slave_sql_verify_checksum=1 ,则 slave的sql线程会参与校验
3 如果在mysqlbinlog工具中使用了 --verify-binlog-checksum 选项则会对binary log进行校验
更改binary log记录条目方式的变量大多数是动态的。
配置并行复制
通常,一个数据库进程会同时给多个客户端提供服务。只要这些请求不通过行锁或表锁来保证数据完整性,它们就能并发执行。在第五章中我们讨论了并发控制。然而在mariadb10.0之前,slave仅使用一个线程用来复制接收到的所有条目。由于这个限制,slave上的写操作性能低下,尤其是在master执行了许多非阻塞写的时候。并行复制通过使用多个并发线程来进行应用复制条目解决这些问题。
如之前所说,并行复制默认不使用。如果想启用,必须设定 @@slave_parallel_threads 变量为一个大于0的值,在master上, 该值是所有slave将使用的并行工作的线程数。
下面介绍的几个参数仅在使用并行复制时才有意义
@@slave_parallel_max_queued 变量用指定数量的内存来缓存那些relay log中到目前还没执行的日志条目。当至少有一个worker线程空闲时,slave就会检查该缓存来找到那些可以并行执行的条目。
@@slave_domain_parallel_threads 在多源复制使用并行复制时非常有用。假设一下,当前有一个slave从三个master上复制数据。假如其中的一个,比如说master1执行了一个要花几个小时的语句。slave就得复制这个语句。同时,所有初始化的连接都会去获取worker线程。但是worker线程已经关联到master 1,而不得不等到这个非常长的语句执行完毕。当没有worker线程空闲时,所有的连接就再也不能从并行复制中获益。
而@@slave_domain_parallel_threads 会阻止一个master独占线程池中所有连接。该变量决定了一台master可以获得的最大线程数。如果这个值不小于@@slave_parallel_threads ,则不起作用。但是如果比@@slave_parallel_threads 除以master的节点数还小。那么有些worker线程则永远不会用到。 该值应尽可能高,以避免在不必要时防止主连接分配线程。(原文写了 to avoid preventing 这个双重否定,实在难以理解,大致意思为这个变量就是限定了一个master可以用的最大线程数) 真实情况下配置最优的@@slave_parallel_threads数会十分复杂。 它高度依赖于master的工作负载状态。按照一般规律,可以配置个比@@slave_parallel_therads小很多的值,在以后可以逐步增加,直至找出最优值。
这些变量都是动态的,因此更改并行复制的配置无需重启master。然而slave还是需要临时重启一下。
让slave慢点
先说一下为什么我们想让slave慢点复制。比如我们想让slave保持30分钟的延迟复制来避免错误。假如我们删了一个表,我们还有30分钟的时间来从slave中还原。或者让slave延迟一天,一周或者一月。可以让我们比较不同时段的数据或者库结构的变化。
MariaDB原生并不支持延迟复制。然而 percona toolkit 工具包中包含了一个工具用来在客户端支持这一特性:pt-slave-delay.应该在所有需要进行延迟复制的slave上安装此工具。该工具通过连接到slave然后定期检查slave和master之间的延迟量来实现。当延迟太低,pt-slave-delay会让slave停止复制一会。
以下几个选项非常重要:
1 delay:设定想要延迟的时间。 --interval选项决定了间隔多长时间检查一次。
2--run-time :决定该工具运行多久,如果没有指定,则程序不会自动停止。默认情况下,当程序终止不再运行时,slave将会重新启动。即便使用ctrl+c来终止程序也是同样的。如果想更改此行为,保持程序退出时slave持续运行,可以使用 --continue选项。(类似循环结构中的break continue)
时间单位可以通过数字+字母的形式来表示,比如 30m 表示 30分钟
举个延迟三小时每十分钟检测一次的例子: 多源复制
之前介绍的所有内容同样适用于多源复制,不同之处在于每个slave通过唯一id来关联到一个主从连接。通过这个id可以精确对每个连接进行配置。(就是通过给连接起别名,之前没有别名, 没法区分。)
比如我们通常不想启停所有的I/O线程,仅是想调整其中的一个。
这样的话,所有的复制相关的sql语句都支持一个参数用来指定对其中的一个连接进行调整,比如: 当然也可以一下子启停所有连接线程:
start all slaves
stop all slaves
当命令中没有指明连接名称的是谁,all关键字也不会出现,此时将使用默认连接。初始值为空。可以通过 @@default_master_connection变量来获取和修改。 当然这个变量还可用于过滤连接。默认的会关联到默认连接。以下语法用来为当前连接指定过滤器。
connection_name.variable_name
比如,如果仅想通过master 1 来复制db 库,而其他的连接复制其他库。可以这么设置:
set global master.replicate_do_db='';
(就是设置参数时候最好指明到底是那一套复制。通过 “名字.参数” 来设定)
复制日志
随着服务器的正常活动和slave的不断复制,使得master的binary log和slave的复制日志越来越大。有必要循环使用旧日志文件或删除不需要的以避磁盘空间耗尽。本节讲解如何完成。
循环 binary log
当前的binarylog名称通过 --log-bin启动参数设定。如果指定扩展名 则忽略该扩展名,并且仅使用基本名称。默认 binary log会写入data目录。如果文件名没有设定,则默认未主机名+ -bin 后缀。
max_binlog_size变量决定了当前文件的最大大小。单位为 bytes。当达到这个大小时,binary log将会循环使用:当前的文件被重命名并且会创建一个新的同名文件。归档的文件遵循 <basename>.<prog_id> 格式,basename即当前文件的basename,prog_id为六位数字构成的序列号。第一个数字为 000001.同时包含一个<basename>.index的索引文件。索引文件名可以通过 --log-bin_index来设定修改。索引文件为人类可读并且包含了当前已存在并且归档的文件,当然,当前使用的binary log文件也包含在其中。该文件不能删除或手工修改。
binary log循环在服务器重启时也会发生。除此之外,执行下述命令也会发生日志循环。
flush logs
flush binary logs
保留好当前已归档的binary log文件。它们可以用于增量备份。对复制仍然有用:记住slave并不会立即应用接收到的日志条目。它会又延迟,甚至有可能需要读取几天或几个月之前的文件。
如果想查看当前存在的binary log文件,可以使用 show binary logs 命令. 对于@@expire_logs_days 变量,如果为非0值,则达到过期时间的文件会被删除(天数)。配置该参数有一定风险,除非你能确保将被删除的文件不会再被其他slave需要。但是当选取了一个安全值时,还需要考虑slave崩溃并且停在一个文件上,或者一系列可能发生网络等问题。这些问题都会导致发生延迟复制。
旧文件可以通过程序进行删除,使用sql语句即可。这种方法更快,但是需要做很多工作,因此一般都是通过crontab 脚本来触发完成。
删除一个不再使用的日志文件步骤如下:
1 执行 show slave status 命令
2 对每个slave都会返回一行记录。master_log_file列表示每个slave当前正在读取的binary log 文件。可以通过脚本对文件名按照字母进行排序。
3 假定当前使用的最旧的文件为 binlog-000050。 如果想删除更久远的文件,可以使用该语句: purge binary logs to 'binlog.000050';
purge binary logs 命令同样可以根据时间来选择,删除指定时间之前的所有文件:
purge binary logs before '2014-04-01 0:0:0';
当一个运行的独立服务器需要变成复制环境中的master时,可以通过备份当前节点的数据,然后导入slave中。这样binary log可以完全删除,因为我们无需再次复制该时间点前的数据。此时,再执行reset master。这个语句会完全删除所有的binary log 文件,清空binary 日志索引文件,并且会重新创建编号为000001 文件用以开始新的复制。
循环使用 relay log
至于写入文件的 relay log,它的命名规则和binary log相同。默认情况下 relay 日志文件遵循如下规则: <hostname> -relay-bin.<prog_id>.hostname为slave的主机名,prog_id 为六位数字的序列号。可以通过-relay-log 启动选项来指定不同的名字。类似的也有 relay 日志索引文件,命名格式为: <hostname>-relay-bin.index。可以通过 --relay-log-index 启动选项进行修改。如果不特别指定,这两种文件都放在data目录中。
如果当前文件达到最大大小限制时,MariaDB会创建新的relay 日志文件。最大relay 大小可以通过max_relay_log_size 来指定。如果设置为0,则大小与max_binlog_size设置的大小相同。
每次slave i/o线程启动时都会创建新的relay log文件。以下命令可以强制创建新的relay log:
flush relay logs
flush logs
当slave的sql线程执行了relay 日志文件中最后一个条目的时候,这个文件就会被自动给删除,除非它是当前文件,没有办法,也没有必要干预这种机制。
slave的日志状态
master 日志信息存放在data目录中的 master.info中。可以通过 --master-info-file启动选项进行变更。relay 日志信息存放在data目录中的 relay-log文件中。可以通过 --relay-log-Info-file启动选项进行变更。
这些文件包含了show slave status 命令的输出信息。然而,当slave线程运行时,该文件中的信息总是过时的,因为复制状态的数据是存放在内存中。只有当slave停止运行时,该文件中的内容才是最新的。
这些文件不会增长也不会自动循环更无需特殊的维护操作。
检查复制错误
计算校验和是最好的用来确保slave中数据准确的手段。这项检查有以下两种方案:
1 在加载数据到slave后,确保每项工作都OK。
2 处在运行状态的服务器,需要定期检查或者当我们怀疑复制错误发生时
事实情况更加复杂,因为在一般运行时slave都会和master有一个延迟。然而, 下面讲解的工具能够通过等待slave 收到某一可靠的二进制日志的事件自动执行此检查。
至少有三种方法可以实现:
1使用 checksum table命令
2 使用 percona 的 pt-table-checksum工具
3 对物理文件计算校验和(仅针对备份)
有时我们仅仅想检查一部分数据的校验和,而非整张表,这时可以通过查询来返回需要检查的数据以计算md5校验和。
checksum table 命令
该命令返回一个或多个表的校验值,语法如下: quick 选项仅仅对 myisam 和aria表有效。这些引擎通过把 checksum或table_checksum选项设置为1来计算一个活动的校验和。使用quick选项,checksum table可以返回一个活动值。
extended选项会通过读取每个单独地行来计算校验和,速度非常慢。
如果不指定选项,默认为quick。
有些引擎不支持这些语句,此时将会返回null。
如果校验和为0,说明表为空,看下例: pt-table-checksum 工具
和介绍过的其他工具一样,pt-table-checksum同样来自于 percona toolkit。它可以用来报告表的校验和和行数。它对服务器性能影响不大,即便针对大表进行校验。
pt-table-checksum对每张表进行校验和计算。 表无需长时间锁定和读取。pt-table-checksum通过语句把大表切分成几个小的子查询。基于服务器的响应时间,pt-table-checksum 组成会对当前工作负载不太重的部分进行查询。此外,它会在每个会话级设置 @@innodb_lock_wait=1, 因此当一个表长时间被另一个会话锁定时,它会断开连接。
默认 pt-table-checksum工具也会检测正在运行的slave,并且连接到它们去执行校验检查。该工具定期执行 show proceslist命令来监控已连接的slave节点。如果其中某些节点落后于master或者断开,pt-table-checksum会处于等待状态直到它们恢复。因此,该工具可以说是用来定期检查数据完整性的最佳工具。
在计算每张表的校验和后,该工具会连接到slave节点并且同样计算校验和以认证表是相同的。然后它会输出具体的校验和和行数。整套流程完毕之前不会去检查其他表。
pt-table-checksum具有良好的容错能力,它可能会因为一些不可控的错误而停止。此时可以通过 --resume来重启检查,之前的进度并不会丢失。
文件校验
在启动slave之前 ,对物理备份拷贝计算校验和是一个的好方法。Linux系统一般都有 md5sum命令,用它就可以直接计算一个或多个文件的校验和。比如: --binary 选项告知md5sum指定的文件默认包含二进制数据,或者如果指定了 --text选项,它会把文件当成文本格式,这仅用在 connect 或者 csv表以及日志文件中。
查询校验和
某些情况下,我们想编写一个仅返回最近插入或者最近修改的那些的行的查询。 我们可以使用这样的查询来快速检查最近复制数据时是否发生错误。可以通过Linux中的 md5sum命令完成: 排错
本小节着重讲解当出现复制错误时的常见解决办法
slave不启动
当执行 start slave时,如果slave不能连接到master我们是不会收到任何错误的。通过执行 show slave status才可以知道 i/o线程是否正在工作。
如果复制没能启动,或者崩溃了。应该找出具体原因并解决。以下几个问题可以帮助你解决很多常见问题,请在遇到错误时多问问自己:
1 slave端的版本号和master是否相等
2 master上是否开启了binary log
3 配置文件中的变量名是否输错?服务器是不是并非以复制身份启动的。
4 master的地址,端口和登录认证信息在slave中是否配置了
5 slave中配置了相应的账户了吗?
6 账户有 replication slave 权限吗?
7 防火墙是否配置正确
8 slave的磁盘是否满了
当slave启动后,除非收到 Stop slave 命令,否则不应该停止复制。然而,如果slave线程没有运行,那么slave很可能出现了复制错误。当然另一个可能是中了MariaDB的bug。 不管咋说吧,我们应该从slave的错误日志开始着手调查。然后, 对最新的relay log文件使用mysqlbinlog程序,基本上就可以找出是哪条语句导致slave崩溃的了。
slave 滞后
通过在master上执行 show binary logs命令可以查看当前有哪些binary log文件。如果文件有很多,那么 至少有一个slave滞后了。 此处定义“太多”并非严格上讲:DBA知道他可以忍受多少延迟或者是想要保持多久的延迟,但这取决于主服务器上的工作负载情况。
如果我们确认确实binary 日志文件太多了,就需要找出到底哪个slave出现了滞后。可以通过 show slave status 命令来查看。通过对比 master_log_file和 relay_log_file列的文件名即可。
master_log_file列包含了当前哪个binary 日志文件正被slave的 i/o线程读取。如果文件名太旧,说明 i/o线程出现了之后。可能是网络出现问题(不管是带宽啊,环路啊,QOS等等之类),不管怎么说吧, 使用连接压缩应该可以解决问题。当然我们自身也要判断选择的binary log 格式是否最合适,因为这个因素会影响信息往返于网络环境的质量。
relay_log_file 列包含了当前sql线程正在读取的relay 日志文件。 SQL线程滞后很常见。在 MariaDB 10 之前,该问题非常难以解决。不过到了MariaDB 10 之后出现了并行复制才大为改观。
如果开启了并行复制, 最常见的问题可能是查询性能较差。不过可以通过检查master上的慢查询日志来找出问题所在。
总结
本章我们讨论了MariaDB中的复制技术
包括复制线程和日志的详细实施技术,以及讨论了日志循环。 即使在不使用复制的服务器上也需要进行这种循环,dba应该确保在所有slave节点成功复制完所有日志之前没有日志被提前删除。
我们还学习了如何配置master,slave以及slave如何扮演master。还学了如何在slave和master上初始化数据以及检查复制环境的工作状态以及如果出现问题的一般解决思路。
下一章我们将会讨论表分区 第十章: 表分区
当表变得非常大时,查询会变得非常慢
其中之一的解决方案就是表分区,这项技术通过把单张表分割成多个物理文件或表空间。每个文件包含表数据的一部分,以此来提高读取速度。因为读写单独的分区会比处理大表更迅速。
本章我们将学习到
1 MariaDB支持的分区类型
2 子分区
3 如何把每个分区分成多个文件
4 维护分区表
5 优化器如何利用分区
支持的分区
所有版本的MariaDB都支持分区技术。不过有以下两种情况是MariaDB不支持分区的。
1 如果MariaDB通过非支持分区参数进行编译安装---即 所有官方发行版都是这样:分区技术默认不参与编译。如果我们通过源码安装,需要指定 --dwith_partition_storage_engine 参数
2 如果MariaDB通过关闭分区功能的参数进行启动---比如启动时指定了 --skip-partition。 这时只要重启别再指定这个参数就可以支持分区了。关闭分区功能也算不上什么性能优化。
检查当前安装的MariaDB是否支持分区技术非常简单。因为分区是以插件形式实现的,我们仅需要查看 information_schema.plugins 表即可。 如果当前支持分区,则会输出类似以上内容,如果当初编译安装时没有加上分区支持参数,则上面的查询不会返回任何记录。如果本身是支持分区,而又是以关闭分区功能参数启动的,则 plugin_status列会显示 DISABLED。
分区在存储引擎级实现,因此并非所有的存储引擎都支持分区。支持分区的引擎有以下几个:
1 innodb
2 tokudb
3 memory
4 aria
5 myisam
6 archive
7 blackhole
对于blackhole来说。当一个分区表被转换成blackhole,随后又转换成innodb时,这张表仍旧是分区表,也就是说分区的定义可以被保存下来。但这项技术并不通用,因为这个转换来转换去的过程事不支持外键列和虚拟列的。
connect和federatedx 并不支持分区,但是它们可以连接到远程的分区表上。
当尝试在一个不支持分区的引擎上创建分区表时,将会收到如下错误: 分区类型和表达式
分区是通过使用分区表达式对每行进行计算的值来完成的。分区类型是一种用于根据分区表达式将每个行分配给特定分区的方法。比如,range类型会给每个分区关联一个取值范围。当一行插入时就会进行分区表达式计算。该行就会被插入到属于它范围之内的分区中。
分区表达式
分区表达式是返回正整数值或NULL的SQL语句,对于时间列,在分区表达式中可以被当做长整型值来返回。然而返回值是不依赖当前时区的,因此不允许使用timestamp和year列。 某些分区类型可以使用返回不同数据类型的表达式,比如像date或者char,这些我们随后就会讲到。
分区表达式必须返回确定性的非常数值,不能使用存储函数和用户定义的函数,即便它们是确定性的。
不允许使用 / 操作符,因为它会返回float值, 即使两个操作数都是INTEGER也不行。DIV和MOD操作符是可以的。位运算符不支持。
分区表达式应该尽可能执行得快。理想情况下应该只包含一列,最好是获取整数值。真实环境下多列参与计算的情况是不可避免的。然而复杂的表达式会影响插入更新和删除的性能。
以下时间函数是经过优化的,可以用在分区表达式中。
year()
to_days()
to_seconds()
其他函数,比如 month() ,虽然并未优化,不过也可以使用。
如果要使用HASH分区以实现良好的性能,那么在表达式中仅能使用一列。 列值和表达式返回值之间也应该存在严格的关系: 列值的变动一定会引起返回值的更改。
举几个分区表达式的例子:
id
id mod 8
year(date)
ord(name)
在随后的章节,我们将会研究如何使用分区表达式
索引和主键
对分区表来说,主键和唯一键有着很大的限制。每个唯一键,包括主键必须被含所有需要参与分区表达式计算的列。
事实上,也意味着:
1 主键必须包含参与分区表达式计算的列
2只允许有限数量的唯一键
比如假定我们想创建一张包含所有员工数据的表。非分区表应该是这样的: 随后,我们想对这张表进行分区。不管因为什么,虽然现在分区并不重要, 但我们希望分区由YEAR(hire_date)来决定。然而,我们需要对这张表修改。
1 第一种方法,我们需要包含hire_date的主键,因为该列要用于分区表达式。然而 hire_date并不满足主键要求,因为它有很多重复值:多个员工可能同一天入职。因此,我们的主键定义就变成了 primary key (id,hire_date). 请注意,此键现在比以前定义的主键更长。因为innodb表上的所有索引都包含主键,所有的索引都会变长。
2 第二种方法,唯一索引说不准对查询有没有用,但是它对约束是有用的,因为它会强制email和vat_id 保持唯一。然而在分区表中,所有的唯一键必须包含所有参与分区表达式计算的列。你可以通过三种方式来完成。可以在把 hire_date 列加到唯一索引中,但是无法保证hire_date 列中的数据具有唯一性( 1中说了)。你可以把唯一列添加到分区表达式,但是会导致插入和更新变得非常慢。最常用的解决方案就是避免使用唯一索引,这在我们以后的例子中可以看到。
(关于上面两点我解释一下,原文翻译过来就这个样子。说一下我的理解: 因为唯一键,包括主键必须被含所有需要参与分区表达式计算的列,所以如果想使用year作为分区关键字,要么把它做成主键,但是没法保证唯一性,所以需要用id列来拼凑一下。或者因为 email和vat_id也是唯一列,如果把hire_date 做成唯一列就行了,但是上面又说了唯一性不好保证。但是不代表不能做,如果这么多列加入到分区表达式计算中,速度肯定很慢,所以最好别使用唯一列)
对于key分区类型来说,主键是必须的。但是其他类型的分区对主键倒是没什么限制。然而,没有键的表速度访问会很慢。分区表不支持外键。不过普通非唯一索引倒是没问题。
分区名称
有些分区类型需要给每个分区指定分区属性。也有些分区只能指定一张表要分成多少区。第一种情况必须为每个分区指定名称,后一种情况,MariaDB会自动命名。 对于自动命名的分区,名称为p开头后跟从0开始的分区步进编号。分区名称大小写敏感并且最大长度为61个字符,比其他对象的标识符略小。
当我们指定分区名称的时候,最好和自动命名的标准类似。然而在某些时候也可以指定一些有意义的分区名称。比如如果有张表,其中的一个分区包含了最近的数据,其他则包含了历史数据,那么包含最近数据的分区名可以指定为 current.这项技术允许我们根据需求来设定分区名称。
分区类型
本节我们将讨论几种分区类型,包括如何工作和创建它们。
RANGE和LIST类型非常相似,并支持相同的管理操作方式。对应的还有一些变体类型,比如RANGE COLUMNS和LIST COLUMNS。
hash和key类型非常相似,与之略有不同的两个类型为 linear hash 和 linear key。
range 类型
range分区类型为每个分区关联不同的取值范围。对每个范围来说,我们只需要指明上边界即可。第一个范围从null开始。也就是最小的值。其他范围都是以前一分区的最大值作为开始的。
看下面的例子: 最后一个分区会把值控制在2020之内。目前看来不算个问题,但是将来有一天还是会出错的。如果我们尝试插入一个范围之外的值,则会收到如下报错信息: 错误可以通过指定ignore子句来忽略,不过行数据也不会被插入。
然而,下面的语法可以帮助我们插入一个范围之外的值:
partition p_name values less than (maxvalue)
此处括号可选。
上面的例子中,我们创建了一张包含了文章的表。此表非常大,我们想通过分区来加速查询过程,因为该表包含非常老的文章,因此使用range类型貌似应该不错,理由如下:
1 year(date) 类型可以作为分区表达式
2 一般查询只会涉及到一个分区
3 一些查询历史的语句才会会涉及到一个或多个分区
4 在将来,如果我们想删除非常老的数据,此时只需要删除最老的分区即可。
list 类型
list分区类型和range很像,只不过list是通过列表列出每个分区关联的所有值而rang是通过范围。每个分区至少指定一个值。分区的顺序和list的类型无关。所有可能值必须被显示指定,并且没有办法把没设定的值放到某一分区中。这点和range的maxvalue倒是不同(原文是 is similar to。。。)。当我们插入一个没有指定的值时,将会收到一个错误: 下例展示了如何创建list分区表: 这个例子中,language 列为存储语言信息的外键。此处没设定外键的原因就是不支持在外键上创建分区表达式。
此处分区表达式就是一个简单的列名。可行的原因是因为 language是一个正整数列。当然,char列也可以的,比如包含语言的iso代码的话。然而这种情况下,表达式会把字符串转义成为数字。
尽管language列是整数,但是这些值不能按照逻辑排序(没有排列的意义), 因此将它们分成几个范围是没有意义的。此处举了几个为什么使用list而不使用range的理由。
1 一般查询很可能通过选择语言来展示文章,因此通过语言分区可以让这样的查询仅使用一个分区
2 比如可以查询某个文章支持几种语言。这将非常快,甚至无需关心是否访问了分区表
3 如果某一种语言出现的比例比较高,那么那种语言的文章就可以单独存放到一个分区。其他分区可以为语言类型分组以控制合适的比例。
4 观众有可能经常阅读至少两个分区的内容。 和之前说的例子类似,如果我们按日期划分文章,当然这不太可能发生。通过language分区可以帮助我们把访问最多的分区切分存放到不同的磁盘上去。这项技术在下面的 分区化的物理文件一节讲解
5 如果某一语言数量增长明显,可以很容易的移动到新的分区中。
columns 关键字
range和list分区有两个叫做 range columns 和 list columns 的变体。当使用它们时,就不再只是单独一个分区表达式了:关联到分区的值是基于多个列构成的列表。这个列表在文档中一般叫做 partitioned columns list (多列分区?)。 不允许使用函数,操作符或任何形式来进行列值的转换。但是对于 columns类型来说,可以使用用更多的数据类型:
1 所有的整型(但仅允许正数)
2 date和datetime
3 varchar,char,varbinary,和binary
不允许使用函数这点非常重要,此处无法把非整型数据转换成整型的。
之前使用的list类型的例子同样可以用在 list columns上,因为它仅仅是包含了一个列名
当需要通过多个列组合来使数据更好地分布时,使用range columns类型会有效。以下例子展示了具体的语法: 这种分区策略反映了一个非常常见的情况:
1 假定当前英文文章比其他语言文章更多, 最合理的划分似乎是英文或非英文
2 在这些组中,我们希望将最近的内容与非最近的内容分开。
注意不能使用year(date)函数。 所以我们选择将年份信息复制到一个字符串列中。 所有字符都是数字 只要所有值由四个字符组成,顺序就不会改变。
//* 注意此处有一个限制--仅能在最后一个分区使用MAXVALUE关键字。比如如果在第二个分区使用 (1,MAXVALUE),看上去貌似行得通,但是会提示语法错误*//
LIST COLUMNS类型对非整数列进行匹配很有用。让我们回到那个使用LIST类型的例子中。我们只能在language中存放成整型值,因为LIST中不支持双字符的ISO代码。但是LIST COLUMNS允许我们这么做: HASH和KEY类型
hash和key类型非常类似, 它们的目的是在分区之间对行提供更统一的分配。分区表达式返回的每个不同的值会以相同的概率关联到每个分区, 一组连续值将被分配给不同的分区。 然而,如果某些值的分布具有非常高的峰值。( 也就是说,有限的一组值经常出现)那么就会导致某些分区会明显比其他分区拥有更多的数据。 如果唯一值的分布是均匀的,则KEY和HASH类型的分区效果会更好。
对于hash和key来说,新行插入的目标分区由服务器自动计算,计算公式如下: MOD为求余运算
选择hash还是key取决于分区想要实现的功能。它们的异同有点类似 list和list columns。
1 HASH类型接受返回正整数或NULL的任何分区函数
2 KEY类型和LIST COLUMNS一样可以允许返回任何类型的值,但只能是单个列。 该列不允许进行计算
对于key类型来说,会返回一个计算过的hash值。hash值是通过PASSWORD()函数计算的来。该函数使用了SHA算法。
通常情况下,创建一个hash分区表的语法如下: 其实大多数时候我们只想确定分区表达式和分区数。所以此处我们使用year(date)作为分区表达式因为所有同年发布的文章将会存放到相同的分区中。某些情况下,这使得我们仅需要访问一个分区即可。
下列语法也是可以的: 允许我们指定分区名称。分区存放的物理文件路径也可以指定,我们放在后面说。
如果想创建key分区,代码示例如下: 上例我们使用了自增列作为分区关键字, 这可以提供最统一的值的分布
同样,我们可以为每个分区指定名字,就像上面的hash分区例子一样。
另一方面,可以使用更为简洁的语法,因为id为主键,所以无需为key分区指定列: LINEAR 关键字
linera hash 和 linear key 分区与hash 和key非常类似,不同之处在于使用了更复杂的算法来让新的行插入到目标分区。linear公式对于正常的数据库操作效率较低.然而它可以使管理类操作更加快速。如果想要提升分区创建,删除,切分,合并的性能。则使用linear关键字很有效。
切分子分区
主分区可以被切分成多个子分区。仅支持一级切分,也就是说,子分区不能再被切分了。每个分区必须设定相同的子分区编号。
只有RANGE和LIST分区的表可以再进行子分区,子分区只能是HASH或者KEY分区。可以使用 columns 和linear关键字。
使用任何其他类型的组合会产生以下错误: 创建子分区的例子如下: 请注意,每个子分区在整个分区中必须有唯一的名称。可以单独指定文件路径。
可以使用更简洁的语法,无需为子分区指定名字或路径: //* 子分区表达式必须显式指定。主键默认用于key分区而非子分区。 *//
管理分区表
分区支持和常规表同样的管理命令,比如修复和碎片整理,当然还有些其他的功能。MariaDB提供了一套sql扩展工具来帮助我们执行所有必要的维护任务。也提供了一些系统表来存放分区的元数据信息。本节将涉及此内容。
获取分区信息
MariaDB提供了几种方法来获取分区信息。
show table status 的 create_options 列包含了分区表的相关信息。
获取人类可读信息的最容易办法就是 show create table 命令。
information_schema.partitions表包含分区相关的内容。每个子分区都拥有单独的一行记录。有些列的含义和table表的列含义相同。此处列出几个和分区相关的列。
1 partition_method:描述了分区类型,同一张表的该列值相同。
2 partition_expression:描述了分区表达式。同一张表的该列值相同。
3 partition_name:分区名称
4 partition_ordinal_position:描述了子分区的位置,从1开始
5 subpartition_method,subpartition_expression,subpartition_name和subpartition_ordinal_position: 这些列和上述几个列相同,但是仅涉及子分区内容。
看如下查询: 如果被分区的是innodb表,并且@@innodb_file_per_table=ON,则每个分区或子分区都会写入不同的表空间。也就是说分区和子分区可以在 information_schema.innodb_sys_tablespaces 表中看到。
分区表的表空间和常规表空间文件的唯一区别是名称格式,如下所示: 请思考下面的查询: (是的,上面这个例子完了以后就本小节就没了)
修改分区定义
MariaDB 支持用多种alter table 命令来修改,创建,删除分区。其中 range 和 list 分区类型拥有丰富的命令集,以及提供了DBA应该注意的一系列警告。hash和key类型支持的命令集就比较少了。因此,不同的分区类型就要分别讨论。
修改 range和list分区
比如现在我们使用article表来进行分区试验: 对于range和list分区类型来说,支持四种操作来对分区进行修改。
1 删除一个分区
2 添加新分区
3 重组一个或多个分区
4 删除所有分区
删除已有的range或list分区会随之删除分区中的内容。比如,如果我们想删除p0分区,那么article表中所有1990年之前发布的内容都会丢失。删除p0分区语法示例如下:
alter table artivle drop partition p0;
添加新分区的命令非常简单,但限制是不能与其他分区的范围或列产生重叠(原文为不能包含其他分区中已有的数据)。对于range分区来说,新分区只能添加到最后。当然,如果添加了 values less than maxvalue 这种分区,并且其中已经有数据的了话。那么添加新的分区只要数据产生重叠,依然会失败。还拿article表来举例,添加新分区命令如下:
alter table article add partition ( partition p5 values less than (2030));
drop partition 和 add partition 命令支持 if exists 和 if not exists 选项。通过这两个选项,可以使得你在删除一个不存在的分区或者添加一个已存在的分区时不会产生ERROR。如果编写到脚本中,可以使得脚本更加完美。以下例子展示了删除一个不存的分区的正确打开方式。 REORGANIZE命令允许我们对分区进行分割或重命名。它通过一个或多个新分区的定义来替换一个或多个已有分区。其限制为不能修改分区的范围,除非是最后一个分区,只有它的范围可以被扩展。 这使得复杂的合并和拆分操作变得不可能,比如想把两个分区切成三个或者把两个分区合并成一个这种就行不通了。
下面的例子中,把p1分割成了两个原始分区(为何此处要用原始,原版?):
alter table article reorganize partition p1 into (
partition p0 values less than (1990),
partition p1 values less than (2000) );
如果想把它们再次合并起来,可以使用reorganize来避免数据丢失
alter table article reorganize partition p0,p1 into (
partition p1 values less than(2000) );
让我们将p4重命名为当前的一个:
alter table aritcle reorganize partition p4 into (
partition current values less than (2020) );
如果想无损移除一个分区的话,可以使用如下命令:
alter table article remove partitioning;
通常不建议这么干。然而 这样做可以使表处于允许我们改变其分区类型或其分区表达式的中间状态,或执行无法使用REORGANIZE PARTITION完成的复杂重组。
修改 hash和key分区
本章我们通过创建article的hash分区表来演示与之前的不同。 hash和key分区支持以下四种操作
1 添加分区
2 合并已有分区
3 变更分区属性
4 删除分区
在修改 range 和 list分区一节中,我们已经讨论了如何以及为何要删除分区。此处不再赘述。
添加新分区非常简单,语法和添加range分区很像。并且没有条件限制。直接执行即可。比如这样
alter table article add partition partitions 2;
这样的语法也是允许的
alter table article add partition (
partition p8,
partition p9 );
这种语法无需为新分区指定名字或路径。如果想通过多个磁盘来提高性能,可以看最后的分区物理文件一节。
hash和key类型不支持DROP操作: 由于它们的性质,破坏分区及其所有数据是不可取的。不过可以通过 COALESCE来把一个或多个分区的数据合并到其他分区,这样数据就不会丢失了。这项操作相对比较快。以下语法演示了如何从article表中移除两个分区
alter table article coalesce partition 2;
注意,这个2是要删除的分区编号。
对于这些分区类型来说,reorganize命令仅仅可以用来重命名分区或改变一些选项。下例展示了如何重命名一个分区
alter table article reorganize partition p0 into ( partition p000);
分区和表间复制数据
此处描述的技术适用于所有分区类型,HASH和KEY类型除外,因为这两种分区会使得逻辑行的行会分布在所有分区上(logical sets of rows are distributed over all the partitions 是因为已有数据会分布在所有的分区上?)。
range和list类型允许我们使用一个或多个值来把数据以一定规律关联到特定的分区中。通常分区表达式比较简单。使得管理员通过查阅选项即可得知哪些分区存放了哪种类型的行。
MariaDB 10.0 支持 通过 alter table命令来在分区表和非分区表之间交换数据。
/* 注意交换数据是双向的。如果我们仅想把一张表的内容复制到一个分区中,就需要先truncate 分区。如果想复制分区内容到表中,则需要先truncate 表 */
现在我们假定article表的定义如下: 下面例子中,我们想要把current分区中的行复制到新建的recent_article 表中。首先先要创建一个和article表一样结构的表。除了不是分区表之外,别的都一样。最简单的办法是复制表结构然后删除分区定义。之后就可以开始交换数据了。 如果我们不想清空原始表的内容,或者MariaDB的版本比10.0更老,那么使用 select ..insert 或者 create table ...select 也行。如果想要在老版本中交换数据,就需要创建临时表了。
当在MariaDB 10中复制分区数据,可以用select的扩展选项,其仅仅会返回特定分区的数据。扩展选项在本章的查询优化部分讲解。但是现在先使用一下,非常容易理解。下面例子展示了如何把current分区中的数据直接写入 recent_article表中而无需删除原始数据。 管理命令
常规表上的管理命令在分区表上同样适用。 一些ALTER TABLE子句可对一个或多个分区独立进行操作,而不比非得是整张表。
下表显示了一些管理语句 list 是操作中必须包含的一个或多个分区的列表。如果想选取所有分区,可以使用all关键字
如果article 表有三个分区叫做 p0,p1,p2 以下三个命令是等价的
1 analyze table article;
2 alter table article analyze partition p0,p1,p2;
3 alter table article analyze partition all;
以下命令仅针对一个分区有效
alter table article analyze partition p0;
以上命令针对子分区皆不可用
对于 CHECKSUM TABLE命令来说,没有对应的扩展语句,但是它可以直接在分区表上使用。
分区的物理文件
如果 @@innobd_file_per_table =OFF ,则创建分区表时,所有分区都会存放到innodb系统表空间中,但是如果设置为1,那么每个分区就会存放成不同的文件。
存储引擎把索引和数据分开存放,比如aria和myisam,都会为每个分区创建一个数据文件和索引文件。
分区的数据和索引文件扩展名和非分区表一样。文件名为表名,后跟随 #p#再加分区名称。名字看起来大概是这样: 和所有表一样,被分区的表定义都会存放到 .frm中。但是它们还有个分区定义文件,叫做 ***.par .
比如假定有张叫做employee 的innodb表,有两个分区,p0,p1。那么它就会有如下文件: 对于ariab表,它的文件构成应该是这样的: 对于子分区来说,每个子分区都会创建一个独立地文件。命名我就不翻译了。此处假定emloyee 表有4个子分区 s0,s1,s2,s3. 子分区多个#SP#标识: 当设定过@@table_open_cache后,我们必须记住每个分区都需要一个单独的文件句柄。
分区文件默认存放在data目录中。当然可以为每个分区的数据和索引文件指定特定的路径。这样可以把文件分散到多个磁盘设备上,减轻单个磁盘的I/O。这项特性非常有用。我们用分区技术的很大原因就是因为可以分散IO。语法大致如下: 这个例子中,如果没有disk_x 目录,则会自动创建一个,用它来存放分区0的数据文件。即便所有的分区都制定了非标准路径,.par 分区定义文件仍然还是在 data目录中的。 /* 如果是分区表,在表级别设定 data directory 和 index directory没什么用,直接无警告忽略。*/
查询优化
如果分区表达式和分区类型已经被恰当设定,那么大多数查询仅仅会针对一个或少数几个分区扫描即可得到结果。
在大多数情况下,优化器会找出哪些分区与当前查询完全不相干。这项优化叫做 partition pruning (分区修剪)。
如果用户通过sql语句来指定扫描的分区列表,这就叫做 partition selection (分区选择)。
/* 然而在MariaDB中,查询永远不会并行化。即便优化器指定一定要读取两个分区,并且这些分区处在不同的磁盘,相同的线程仍然只会依次扫描它们。 */
partition pruning 分区修剪
MariaDB中,分区修剪可以用在range和list分区中,不适用于 range columns 或 list colums。当一条语句引用了分区表达式使用的列,优化器通常可以根据查询计划来排除一个或多个分区。通常可以排除到只剩一个分区。
优化器检查以下语句中的WHERE子句,以确定是否可以应用分区修剪
1 select
2 insert ... select
3 replace ... select
4 delete
5 update
对于insert来说,优化器会检查插入的值。对replace来说,where语句和新值都会被检查。
当优化器分析where子句时,涉及到以下操作符是可能会进行修剪: 如果分区只包含分区表达式返回NULL,is null 和 is not null值的行,也可以参与分区修剪。
可以使用explain命令来获得sql的执行计划。这点在第三章已经讨论过。现在如果处理分区相关问题,就需要涉及explain的扩展选项—— partitions 关键字了。它会在explain的输出中添加分区的列。其中包含了执行该语句会涉及到的分区名称。通过添加partitions 可以检查分区修剪是否以及如何应用的。看下例。
下例中我们使用 article表,以ID列的范围作为分区。 基于id排查的where条件使得该语句可以通过分区修剪获益。比如这样: 只有相关的分区会被扫描。因为第一个分区包含小于5000的数据,优化器知道它不包含相关数据,因此p0不会被访问,再看一个: 此项技术可以在跨越多个分区的范围的情况下完美运行,可以看到p0,p4分区都没有被访问。 如果查询仅返回一行或只检索一个值,此时应该仅访问一个分区。看下面例子: 因为没有分区包含我们需要检索的值。extra列中也清晰地告知我们,在检查分区定义后找不到符合要求的分区。因此查询根本不会执行。再看一个: 这个例子仅仅展示一下在指定多个条件时分区修剪依然可用。
我们也在list分区表上做一些查询,不过仅仅用于说明分区修剪在list类型上的工作情况。表定义如下: 现在,先看个例子: 仅有第一个分区包含1,所以其他分区被修剪了。 看到哪怕是对不连续的分区,仍然可用。 在多个分区中检索也可按预计的进行。
partition selection 分区选择
分区修剪技术可以帮助优化器自动决定哪些分区一定会被访问到。在mariadb10.0中还提供给用户一个可以显式声名一定要扫描哪个分区的技术:partition 子句
该子句可以在任意表后面指定,下列语句都支持使用partition: partition子句的语法如下: partition_list 为一个或多个分区名称,以逗号隔开。子分区也可以写入列表,但是要将名称与其主分区名称相连。
比如对于tab表,以下命令会返回p0和 p1中的s3 子分区中的全部数据:
select * from tab partition (p0,p1s3);
如果指定的分区不存在,那么整个语句都会执行失败并收到如下报错: 现有分区可以以任何顺序使用多次,这点对子分区同样适用,即使它们的主分区也包含在列表中。(其实就是partition_list中的分区名称可以出现多次,并且没有顺序要求。无论分区还是子分区)。
因为分区修剪可以自动处理并且在MariaDB中比分区选择出现得更早,导致这项特性看起来没什么用。但是仍然还是有不少优势,比如:
1 它适用于任何分区表达式
2 适用所有分区类型
3 可以让我们指定子分区并且不会被自动修剪
4 假如有bug,分区修剪不可用了。
5 对于复杂查询,它可以令优化器明显提速。
6 它有时可以比WHERE子句更详细
7 如果where子句不正确,会导致意外的数据损毁。添加额外的partition 子句可以减小这种风险的概率。
8 类似地,可以将PARTITION子句添加到添加大量数据的语句中,以避免写入不正确的值。这项技术使得在批量插入时,哪怕只有一行数据不满足要求,插入都会失败。并且ignore子句不会起任何作用
9 它允许我们一次快速查询一个分区或子分区来分析总体数据分布
总结
本章我们学习了如何使用分区技术来优化大表
学习了MariaDB支持哪几种分区类型以及如何编写一个好的分区表达式。我们通过几张简单的表学习了如何从不同的分区策略中获益。还讨论了子分区技术,学习了通过sql语句来管理分区。最后,还研究了优化器如何通过语句来排除不相关的分区以及如何强制服务器来扫描哪些分区。
下一章我们将会讨论在多台服务器中如何实现数据分布。 第11章 数据分片
本章我们将会讲解MariaDB中提供的三种重要的数据分片方式。
1 通过多路磁盘均衡IO
2 通过 FEDERATEDX或CONNECT构建简单集群
3 SPIDER引擎
通过多路磁盘分布存储文件
数据库服务器的性能瓶颈通常为I/O能力不足。读取或修改不在内存中的数据需要访问存储设备。当然,你要是舍得花钱,比如买SSD之类的,那另当别论。然而不论你怎么买买买,最终还是会出现当前存储设备速度太慢不足以满足与日俱增的高并发事物。解决这些问题的主要方法就是配置cache。第六章也说过了。然而,有时候经常访问的数据集比RAM还要大不少。同样日志也需要频繁地写入,虽然一个好的配置策略可以缓解这种情况,但数据库总归还有大量的数据还是要写入磁盘的。
此外,存储设备的容量有限。一个存储设备肯定塞不下整个大型库。那么本章就来讲解一下如何通过多路磁盘设备来分布存储物理文件。
决定表文件的路径
当创建表时,服务器默认会在data目录中创建一个文件。默认路径通过 @@datadir变量设定,注意并不是动态的,仅能在配置文件或者启动服务器时通过 --datadir来设定。
如果想给一张表的数据和索引指定不同的路径,可以通过 data directory 和 index directory 分别设定。单独设定 data directory 不会影响到索引文件的路径。
如果指定路径不存在,则会报错。
对于那些不能为每个索引设定路径的引擎来说,比如innodb。强行设定 index directory 会生成如下警告。 看如下例子: data directory 和 index directory 仅可以在创建表的时候指定。如果我们尝试通过alter table 来修改,则会收到如下警告: 对于分区表来说,data directory 和index directory 选项可以为每个分区独立设定。这一点我们在第十章的时候已经说过了。
InnoDB可以通过 @@innodb_data_home_dir 来设定一个单独地目录。默认情况下 innobd使用 data目录。
如第七章所说,innodb有一个 file-per-table 模式。其通过设定 @@innodb_file_per_table=ON来开启。此变量在mariadb10 中默认为on,老版本不是。如果开启此功能,任何表都可以通过 data directory 选项来创建。如果为OFF,那么所有的表都会被存放到系统表空间。系统表空间存放在data目录中。所以最好给存放系统表空间的目录挂上高速存储设备,比如raid或者ssd之类。
默认情况下,data目录中也会存放服务器日志。日志在第二章,第八章已经介绍过了,具体如何修改路径,请查阅相关章节。
innodb 日志文件
innodb在执行事物时有两种特殊的日志。
1 undo log
2 redo log
undo log 用来在事物失败,或状态不完整时进行回滚。 数据的更改总是对数据本身(在缓存或磁盘上)进行,而不是单独的副本。因此,一旦事物回滚,数据就会回退到被修改之前的状态。undo log保留了原始数据的一份拷贝以及恢复所必要的信息。
如第六章所说,所有的修改通常会写入到buffer pool中。内存中被修改过的页叫做脏页。这些页需要被刷写进磁盘以保证数据变更被持久化。然而在他们刷写之前如果发生灾难,比如MariaDB崩溃,或者断电。 InnoDB必须保护数据免受类似事故的影响。为避免不良影响,被修改的数据会优先写入磁盘的redo log 中。如果发生灾难,当MariaDB重启时,innodb会重现已经写入redo log的所有事物。
这些日志必须被迅速写入磁盘。因为大型事物会导致事物日志非常多。因此最好是把日志文件存放在不同的磁盘上。
这两个日志都会有许多非顺序写入,因此最好使用ssd设备。
配置undo log
默认情况下,undo log 存放在系统表空间。如果想存放到不同的路径,或者不同的存储设备。那么必须执行以下几步。 1 @@innodb_undo_directory: 此变量决定undo log 在哪创建。
2 @@innodb_undo_tablespaces: 此变量表示写入undo log表空间(文件)数。 如果其值为0,默认undo log写入系统表空间,其他情况下,undo log 会写入由 @@innodb_undo_directory设定的目录。最大值为126.这两个变量都不是动态的,并且必须通过配置文件或者 --innodb_undo_directory 和 innodb_undo_tablespaces 启动选项来指定。
基于性能考虑,也可以设定 @@innodb_undo_logs变量,此为动态变量。如果performance_schema表经常显示undo log上面有互斥锁,那么为每个日志文件增加段的数量是个不错的办法,最大值为128。
/* 需要注意的是,当服务器运行时,undo log文件的数量肯定不会减少。也不应该去增加,除非我们确定有必要来减少锁争用。在生产环境上调整该参数最好还是先测试好 */
(注意此参数经查阅资料和试验,必须在数据库初始化之前配置好。以后再配置会报错。原文并未提到)
配置redo log
redo log受以下三个变量影响
1 @@innodb_log_group_log_dir: redo log 文件存放的路径
2 @@innodb_log_files_in_group:redo log 的文件数量
3 @@innodb_log_file_size:每个独立文件的大小。
这些变量都不是动态的。
redo log 的文件命名以ib_logfile 开始,后跟步进编号,从0开始。innodb开始写入第一个文件直到它达到最大体积。当最后一个文件达到最大体积限制时,innodb会重新使用第一个文件。 所有文件的总最大大小都有限制,数值接近512GB。设定为500GB比较保险。
默认redo log 存放在MariaDB的data目录中。分别是 ib_logfile0和ib_logfile1。每个文件的最大限制为48MB。
如果为了减轻主磁盘的IO,需要把redo log 文件移动到单独地设备上时,不推荐改变log文件的编号。
//* 过大的@@innodb_log_file_size在mariadb5.5之前会导致恢复巨慢。但是新版本上不会了 *//
FEDERATEDX 和 CONNECT 引擎
FEDERATEDX和 CONNECT 允许我们在本地服务器上使用远程节点的表。 本地的 federatedx和connect 表扮演着客户端和远程服务器之间的代理角色。当客户端发送sql时,本地表会把sql发送到远程服务器;当远程服务器返回结果集时,表会把结果转发给客户端。
当然这并不是在多个服务器间共享数据的最佳解决方案。spider引擎就可以做得更好。然而,federatedx和connect引擎也有其一定的优势。
MariaDB knowledge base这本书说到,federatedx最早被cisco公司开发。因为他们的设备本身没有那么多的存储空间,因此需要MySQL存储引擎来访问远程数据。federatedx之前在MySQL 5.0中叫做federated。在MySQL 5.1中添加了许多功能。。因为MariaDB的开发者认为oracle可能并不太乐意维护federated,他们就自己搞了一套衍生版,即现在MariaDB中的federatedx。这套衍生版的作者就是federated开发者本人。衍生版完全兼容,并且还修补了很多bug以及带来了一些新的特性。最值得一提的就是支持事物(前提是下层目标表支持事物)以及ODBC协议。
connect引擎在本书已经多次提及。它基本上允许用户像访问MariaDB表一样访问各种外部数据源。包括非关系型数据源,比如文本文件;CSV,XML,HTML,和INI 等等。并且文件也可以使用gzip进行压缩。甚至有些奇奇怪怪的数据源也可以得到支持;比如在windows上,即便是目录和MAC地址也可以当做表来被读取。与远程数据库服务器进行连接也是它支持的数据源之一。包括像MariaDB/MySQL的协议以及ODBC都是支持的。connect引擎在maraidb10中引入。
尽管federatedx和connect都可以传输远程MySQL的表或视图,但是它们不同的历史背景决定了它们拥有不同的特性和优势。
下面列举几个federatedx和connect的特性
1 当创建本地表时,列定义可以忽略,因为可以直接使用远程表定义。
2 可以从本地表中排除一些远程列。
3 本地表不能添加列,但是对于connect来说可以添加虚拟列。
4 本地不会创建索引,这没毛病,因为修改远程节点数据无需访问本地表。
创建federatedx 表
为了向后兼容,federatedx在MariaDB中被称作 federated。它属于内建的插件,因此无需安装也不能卸载。
让我们通过几个例子来看一下如何创建federatedx表。首先需要在远程节点上创建需要传输的表,远程节点就叫做remote吧。什么引擎都行。然后看例子: 然后在其他服务器上创建federatedx表。本地节点就叫做local吧。代码如下: 无需提供表的结构,因为它会根据远程表的架构自行判断。connection选项用来指定连接到远程表的必要信息。此连接串的含义如下:
1 使用的协议: mysql://
2 用户名 user1
3 密码 pwd
4 远程节点的主机名或ip地址: remote_server
5 指明数据库 : db1
6 远程节点的表名 : user
如果未指定,则数据库名称和表名与用于FEDERATEDX表的名称相同
//* 如果federated引擎版本非常老,则存储连接远程节点信息的选项是comment,而不是connection。坏处就是 用户无法将注释关联到FEDERATED表。当前版本的引擎都使用connection了。*//
定义远程服务器连接
之前我们展示过创建federtaedx连接一个远程表的方法。非常方便。如果我们想访问远程节点的多个表,我们又不想重复输入冗长的连接字符串时又该怎么办?
可以为远程服务器或远程数据库定义一个连接(类似dblink)。通过create server 命令即可完成。通过这种方式创建的连接可以用在所有需要访问远程表的引擎上。比如 federated ,federatedx,connect,spider都没问题 以下是个create server的例子 没有办法来指定表名
然后就可以通过连接来创建新表了: 此处连接字符串通过 定义的连接+表名即可。如果不指定表名,则假定要连接的表是和当前创建的表同名。
和存储在MySQL 系统数据库中的所有表一样,存放连接信息的servers表同样只能查询不能直接修改。如果想查询内容,可以通过如下命令: 请注意,唯一支持Wrapper的只有MySQL,当前不支持Owner属性。
如下命令可以删除一个链接 已有的链接不能被编辑,但是可以删除和重建。删除或重建不会影响当前已有表。如果想更新表定义,就必须删除并重建它。
创建 MySQL connect 表
connect 引擎支持多种类型的表。每种都允许我们使用不同类型的数据源。数据源使得我们可以通过MySQL或ODBC来与其他远程DBMS服务器进行通信。MariaDB和MySQL使用MySQL 连接协议,它属于原生协议。ODBC可以在任意支持ODBC标准的DBMS之间工作。此处我们仅讨论MySQL 连接协议。
创建federatedx表的语句也可以用来创建MySQL connect表。 但不能定义任何索引: 和federatedx很像,connect同样需要connection选项来指定连接串。
同样也可以只指定服务器名称 connect引擎也支持dbname和tabname选项,用来指定远程数据库名称和表名。 它们可以与连接字符串组合,如下所示: 如果这时候connection里面又指定了一次数据库名称和表名,则以连接串中所指定的为主。
可以指定视图名称来替代表名,举个例子: 发送sql语句到远程服务器
connect引擎允许我们发送任意的sql语句到远程服务上。非常有用,比如说你无需登录远程MariaDB服务器即可完成管理或者创建表之类的操作。
如果想直接发送命令到远程服务器,那么就必须创建一个特殊的connect表: 表和列名不相关,可以任意定义。我们使用了受管服务器的名称,后跟_sql后缀。这样看上去比较有逻辑性,如果我们决定定义一个特殊的connect表来给所有的远程服务器发送命令。即使我们决定使用FEDERATEDX或SPIDER访问远程表,这也是有用的。但是,在设置这些特殊表的权限时,我们必须谨慎。通常仅能被root或者那些有super权限的账户所访问。
(这一段前言不搭后语,没什么干货,1 注意connect表命名要有含义,复合逻辑 2 注意权限问题)
OPTION_LIST选项会使得connect引擎意识到这是一张与众不同的表。其关键选项是Execsrs。Maxerr选项可以用来设定能当我们发送每条语句时,可以从远程服务器接收到的最大数量的错误和警告。
表中的每一列都有特殊的含义。 如果我们使用有意义的名称,则它们的功能会更清楚。列的名称并不主要,列的含义是由FLAG来决定的。比如,重命名message列并不会改变它的功能,因为它的flag没有改变。关于flag的含义,下面给出的表能说明一切 flag 默认是0,因此create table命令中,flag=0可以省略。
下例展示了如何通过之前创建的表来执行一条sql语句: 执行的语句通过where条件发送并且通过statement列进行返回。我们知道语句执行成功的话,warning应该是0.从message和number的表现情况来看是没有问题。
如果只是为了验证create table语句在远程节点上是否可以执行成功,那么可以这样使用connect表: 当然可以一次性发送多条管理命令。使用IN操作符即可。会为每个执行的语句返回一行。比如这样: 默认情况下,只返回严重错误,它们包含在message(FLAG = 2)列中。 在大多数情况下,这是足够的,但有些情况下,我们倾向于评估每个警告以调试消息。为此,我们可以发送三个特殊命令到该表。connect引擎会解析这些命令并且并不会发送到远程服务器: 它们会告知connect在结果集中展示notes,warnings和errors: 上例中,第一个note信息告诉我们想要删除的表不存在。最后的两个警告告诉我们因为myisam输错了所以自动设定为InnoDB存储引擎。
此技术不直接提供从远程服务器检索结果集。然而仍然可以通过一些简单的步骤完成:
1 发送create table ...select 语句到远程节点
2 创建connect表设定新的远程表
3 查询本地表
注意,一旦远程表改变,则本地表必须重建。除非列都一样。
下例展示了如何检索远程服务器的版本号: 使用connect引擎合并多张表
另一个非常有用的TABLE_TYPE选项就是tbl了。通过它可以把多张相同或非常相似的表合并起来。tbl使得我们可以非常容易地把数据分片到多个服务器中。其方法为定义一张本地connect表,通过tbl关键字来连接到多个connect表。对tbl表的查询会把sql语句转发给各个connect表,进而转发到远端MySQL表,然后数据会从远端返回至本地最终返给客户端。
注意tbl选项的限制:只读。如果指定了tbl选项,则远端表不能被修改,这就使得在很多情况下达不到要求了。但是作为在多个MariaDB节点间共享只读数据还是没问题的。
创建带有tbl选项表的例子如下: <table_list> 变量是以逗号分割的connect 表名称的列表。每个名字都可以通过 database_name.table_name来指定。如果数据库名称没有,则假定该表位于与TBL表相同的数据库中。
对于tbl表来说,技术上讲,只能建立在CONNECT表上,它仍然可以间接链接到使用不同存储引擎的表,例如InnoDB或MyISAM。 这是通过创建指向这样的表并在其上构建TBL表的MYSQL表来完成的(这是通过创建指向这些表的MYSQL表来完成的,并在它们上构建一个TBL表。翻译成什么的都有,我贴一下原文:This is done by creating MYSQL tables that point to such tables and build a TBL table on them. 并做一下解释。意思是说tb表只能连接到connect表,但是如果想连接到本地表呢?比如innodb或者myisam,这样先创建本地需要的表,然后创建connect表来连接到本地表,中间通过connect引擎做一个隔离来使得tbl可以访问常规的innodb或myisam这种。我是这么理解的,因为套了一个中间层,肯定要比直接访问慢,但是肯定又比通过网络访问快。 )。但是结果将比直接访问慢,因为将使用到本地服务器的连接。但是毕竟比连接远程服务器要快。如果我们希望通过多台服务器(包括本地服务器)分发数据,这种技术非常有用
CONNECT引擎可以解决使用其他存储引擎(如FEDERATEDX或SPIDER)无法轻松解决的性能问题。假定我们为一家有几家实体店的公司工作。每家门店都有自己的数据库,其中有库存产品,产品类别和最近交易等其他数据。假设我们要写一个查询,需要返回上周在所有商店销售的手机平均数量。此时需要连接以下三张表: product_category,product,transaction.
但是,此JOIN必须分别针对每个商店执行; 将一家商店的交易与另一家商店的商品进行比较会不会出错还不好说,具体要看系统是如何设计的,但毋庸置疑的是这样需要大量的流量,速度会非常慢。要在门店的基础上执行JOIN查询,我们可以为每个门店创建一个MYSQL CONNECT表;该表将基于该查询,使用SRCDEF选项,如以下代码所示。这些表在查询时将返回每个商店的类别,产品和交易之间的关联数据。然后,我们可以在这些MYSQL表之上构建TBL表,并执行返回所需平均值的查询。这样只有相有用的数据才会从门店的数据库发送到本地的MariaDB服务器。
可以使用特殊语法来实现相同的结果,因此无需为每个远程服务器定义新的CONNECT表。在tbl表定义时加上SRCDEF选项即可。比如这样: SPIDER 存储引擎
与FEDERATEDX和CONNECT引擎一样,SPIDER也可以像在本地服务器上一样访问远程表。然而spdier引擎是专为数据分片设计的。主要功能还是通过访问单张本地表来检索多个远程表。
使用表分区可以在SPIDER中实现数据分片。如果把spider表分区,每个分区可以关联到不同的远程表。spider引擎非常适合range 和list分区,包括 range columns和 list columns。
spider支持常规sql事物和XA事物。前提是远程表也支持。
SPIDER存储引擎最初是为MySQL设计的,与MariaDB一起发行的版本会稍作修改,以利用MariaDB特有的功能。
spider的作者是 Kentoku Shiba. 项目地址为: http://www.spiderformysql.com/
SPIDER引擎的工作原理
SPIDER存储引擎本质上实现了本地服务器的优化器与对端存储引擎进行通信的过程。当优化器为关联到spider的查询选择了执行计划后,spider会扮演成MariaDB客户端,把这个计划转义成信令发送到一个或多个远程服务器。
当使用spider引擎来向远程服务器插入数据时,其内部使用了二阶提交事物。使用一阶提交的问题就是仅能保证关联到一台服务器的数据完整性。当所有修改都得到响应时提交才会生效。 但是假设修改涉及两台服务器,我们将命令发送到这两个服务器,并且没收到错误。随后,我们在服务器1上发出提交,并且成功。 最后,我们将提交发送到服务器2.如果此提交失败,则出现了不一致。事实上,我们要求的变更在服务器1上已经生效且不能被撤销。因此一阶提交不适合在多台服务器之间执行事物。
两阶段提交事务模型与用于XA事务的模型相同。 用户可以发送 XA命令到,因为SPIDER完全支持它。 但是,即使使用正常事务,SPIDER也会使用两阶提交来使变更在多台服务器之间生效。对于这项技术,当服务器收到提交时,并不会立即应用变更。虽然 它知道事务完成,但它会等待第二次提交。如果任一远程节点返回错误或不可达,spider就会在每个服务器上回滚该事物,相关数据会被消除。仅当第一个提交在所有节点上全部成功后,spider才会给所有节点发送第二个提交。第二个提交使得数据生效。
当查询涉及多个SPIDER分区或多个未分区的SPIDER表时,它们会被分解成多个线程。 每个需要被查询访问的远程服务器使用单独的线程。 考虑到这一点,DBA可以通过添加多个分区来指向多个远程服务器以使得扩展查询并行化。
查询结果由SPIDER缓存,直到它们被发送到客户端。不完整的结果集可以存储在远程服务器或本地服务器上。
SPIDER存储引擎在内存中维护远程表和索引的统计信息。这些数据定期更新。和其他引擎一样,SPIDER通过访问这些统计信息来选择最优的执行计划。
安装spider引擎
SPIDER引擎在官方发布的MariaDB中已经内建了,但是默认并未开启。在使用之前,需要做以下几步
1 安装插件
2 执行install_SPIDER.sql
和所有插件一样,SPIDER引擎可以在服务器运行时通过 SQL INSTALL命令进行安装。
install_SPIDER.sql文件的具体位置取决于MariaDB的发行版和操作系统。一般都在share子目录中。这个文件会在MySQL数据库中为SPIDER创建所需要的系统表。
以下为安装SPIDER的过程 然后检验spdier是否安装成功 创建SPIDER表
SPIDER存储引擎支持特殊语法来指定远程表的位置。 此语法与用于FEDERATEDX和CONNECT不同,必须在表选项中使用COMMENT 。
以下示例显示如何创建一个连接到远程表的简单,未分区的SPIDER表。此处将使用我们已经用于一些FEDERATEDX和CONNECT示例的user表: 如果table没有指定,则选择和本地同名的表。如果database没有指定,则选择包含当前本地表的database名称。
我们还可以使用已定义的服务器名称,如下所示: SPIDER存储引擎可以自动发现远程表的结构,并创建一个相同的本地表。 因此,我们可以简单地写 SPIDER存储引擎表在分区时非常有用。每个分区可以指定不同的远程表。以下例子展示了如何创建SPIDER分区表 以下表选项可用于创建 使用SSL来连接到远程服务器的 SPIDER表或SPIDER分区表(请注意断句): 关于MariaDB ssl的详细信息,参见第五章内容。
记录查询和错误
用户针对SPIDER表的查询可以被写入一般查询日志。当然, 如果由SPIDER生成的命令返回错误,那么远程服务器也会在错误日志中记录下来。这种行为取决于@@general_log和@@log_error参数的设定。
然而,当SPIDER表查询一个远程表时,远程服务器默认不会记录命令。除非远程服务器设置了@@ SPIDER_general_log=ON。 SPIDER命令将写在通用查询日志中。
当SPIDER生成的命令在远程服务器上返回错误时,通过设定@@SPIDER_log_result_errors=ON 可以使得本地节点在日志中记录该错误。
在远程服务器上执行任意语句
有些用户定义函数(UDF)提供了一种简单的方法实现对远程服务器执行任意SQL语句的功能。SPIDER引擎中就有这样的UDF函数。
和 MySQL connect的SCRDEF选项不同,这些函数会返回结果集。
//* 请注意,在任何情况下,可以调用这些函数来远程执行SQL语句。 实际上,虽然它们诞生的目的旨在协助管理基于SPIDER的集群,但是不适用SPIDER引擎也完全没问题 *//
浅析SPIDER_direct_sql() 函数
SPIDER_direct_sql()函数允许我们对远端MariaDB或MySQL执行任意的sql语句。查询的结果将复制到临时表中,因此需要在调用此函数之前创建该表。记住这张表一定是临时的。
看例子: 让我们仔细看看这个函数,它有三个参数
1 我们想在远程服务器上执行的sql语句
2 想要存放结果集的临时表名称。注意表需要提前创建。此函数没有给你临时创建表的功能。
3 访问远程服务器必要的参数。语法和创建SPIDER表相同。同样也可以指定已定义的服务器名称。
浅析 SPIDER_bg_direct_sql() 函数
当调用 SPIDER_direct_sql()事,当前链接会一直挂起直到远程命令完毕并且结果集已经存放到指定的临时表中。然而有时候我们想执行一个长查询,并且不想让当前链接一直挂起等待它完成。此时可以调用SPIDER_bg_direct_sql()函数。见名知意,它会在后台执行sql。
至于语法,和SPIDER_direct_sql()一毛一样。
总结
本章我们讲解了当I/O变成系统性能瓶颈时如何通过多路磁盘来提高性能。表文件和日志文件可以存放到不同的磁盘设备。尤其是把innodb表和日志移出系统表空间对性能提升巨大。
还讨论了如何通过在多个服务器上分布数据来均衡IO。MariaDB提供了三种存储引擎来实现该功能
1 federatedx
2 connect
3 SPIDER
federatedx引擎被设计用来访问单张远程表。CONNECT存储引擎用于访问各种格式的外部数据。SPIDER存储引擎被设计用于使用MariaDB存储引擎的API来实现表集群。在以上情况中,本地服务器和远程服务器之间的通信对于用户来说是透明的,查询FEDERATEDX,CONNECT或SPIDER表和查询常规表完全看不出区别。
在下一章,我们将会讨论如何通过galera实现MariaDB集群。 第十二章 MariaDB galera cluster
本章我们将会讨论MariaDB galera cluster 。这项技术是由多个MariaDB节点组成的高性能,高可用的数据冗余解决方案。以下几点将是本章讨论的主题
1 MariaDB galera cluster 的概念
2 安装节点
3 启动节点和配置集群
4 使用 galera arbitrator 处理集群脑裂问题
5 诊断和解决性能问题
6 通过使用 galera load balancer来分散集群负载
MariaDB galera cluster 要点
galera 集群,简称为galera,是一种由MariaDB和MySQL组成的集群。项目地址是: http://galeracluster.com/ MariaDB galera cluster是MariaDB官方发布的集群解决方案。要求MariaDB的主版本号必须相同。第一版跟随5.5发行,其他发行版,比如 percona xtradb cluster 则是基于 percona server的。
MariaDB galera cluster 可以通过MariaDB 的官方repo仓库进行安装和升级。或者也可以从官网下载相应的Linux二进制安装包。在MariaDB knowlegdge base中也包含了一部分galera的内容。当我们在此书找不到需要的内容时,可以去http://galeracluster.com/documentation-webpages/ 看看
galera 集群概述
galera 集群通过在节点之间使用多源同步复制来传播数据。集群中的所有节点都会接收用户请求,看起来就像单台服务器一样。当作为galera cluster集群的一部分,我们应该意识到到什么可以做,什么不可以做。最主要的限制就是galera只能运行在Linux上。并且只有innodb可以完整地支持galera。有试验表明对myisam的支持也可以实现,但是最好别用。在本章随后部分将会列出完整的限制。
集群中的节点数量没有下限或上限。然而,如果集群需要保证高可用,则至少使用三个节点。
Galera不使用松散的一致性模型,例如大多数MySQL产品一样使用完整的一致性检查。它提供了DBMS通常所需的高一致性级别。不管用户发送sql到哪台节点,写入都会以给定的顺序进行。节点间没有数据同步上的延迟。但在低速网络中,需要注意网络延迟,但它只应该涉及提交。并且在给定时间发送的查询将始终返回相同的结果集,不管连接的是哪台节点。这归功于同步复制技术。
因为它的性质, Galera可以用于几个目的。可以用在负载均衡,因为所有节点的数据都在同步增长。 如果节点在地理上非常遥远, 客户端甚至可以与最近的节点交互,减少延迟。galera同样也可以把一个或多个节点用作数据的备份,避免在恢复正常备份时导致最近的数据丢失,或者也可以当做一种传统的复制系统,即所有的客户端请求都在一个节点,其他节点作为slave。
//* galera中,每个节点使用多个slave线程,mariadb10.0支持多线程复制,这项特性在5.5版本时引入*//
Galera群集适合云计算,因为使用了节点间自动配置技术,使得缩放变得非常容易。
同步复制
所有MariaDB和MySQL的复制方案,包括内建的复制,都是异步的。异步复制保证了 master端上发生的写操作都将传播到所有slave中。 但是,不能保证什么时候会发生。实际上,在一些繁忙的系统中,主从两端的数据延迟达到几小时或者几天都不是奇怪的。
同步复制的特性使得主从两端的数据是同步的。事物同时进行不会产生延迟。如之前所说,同步复制有两个重要意义:在主节点宕机之后不会产生数据丢失以及事物以相同的顺序被所有节点执行。
然而实施同步复制对开发人员来说还是有很强的挑战性。传统意义上说,它使用分布式锁或者二次提交技术。比如spider就使用二次提交。这些方法都比异步复制慢很多。galera使用不同的模型,即基于事物认证。当一个节点收到来自客户端的sql语句时,执行该语句并把写操作传播到其他节点而无需等待提交。事物的过程是并行的。 每个节点应用写入,但不使它们生效。如果修改成功,节点就会确认此次操作。当第一个节点收到一个提交和所有节点的确认时才会使写操作生效并使所有节点提交。事务在传播之前会重新排序, 这降低了它们在一些节点中因为冲突而失败的概率(或者说整个集群中)。 含有隐式提交的语句会与其他写操作隔离。该模型是近期在学术上关于对群组通信和重排序技术的实现。该学术的具体内容,见http:://infoscience.epfl.ch/record/32566/files/EPFL_TH2090.pdf
Galera中的每个节点内部仍旧使用innodb提供的传统事物解决方案。这种机制会在每个节点自动执行。因此,galera使用的这种复制技术也被叫做虚拟同步复制。
同步复制是通过wsrep完成的,MySQL和MariaDB都提供了该API。 Galera可以被认为是这个API的实现,并作为了MariaDB和MySQL的插件。因此它也被称作是wsrep实现的提供者,将来也许还会有基于galera开发的产品出现。
在 Launchpad项目中如是描述wsrep: 具体内容可以参见:https://launchpad.net/wsrep
配置一个集群
(安装步骤你们看看就好,这些步骤还不如百度出来的帖子靠谱,但是理论知识比百度的强。译者注)
本章将会讨论如何配置一个集群,不过先让我们看一下都需要提前准备什么。
需求:
如之前所说,所有节点必须运行在Linux系统上。
硬件上倒是没特别的需求;事实上,如果服务器可以正常运行独立的MariaDB服务,那么也就可以充当galera节点。在正常操作下。使用galera 复制占用的内存非常少,几乎可以忽略。唯一注意的是,当从一个节点拷贝数据到另一个节点的时候需要耗费大量内存。一般在新节点加入,或者当已经断开的节点重新加入时。然而,当对节点进行硬件选型时,需要注意集群的速度取决于集群中最慢的那个节点。
两个节点间的连接必须足够快以应对负载需求。尽可能保证MariaDB galera cluster运行在不含其他主机的子网中。(节省背板带宽),因为galera复制需要和集群中所有节点进行通信,网络系统中不相关的流量将会影响整个集群性能。同样,阻止额外的主机可以直接连接到集群节点同样也算一个安全策略。
当然,节点接收客户端的请求需要更多的内存。我们可以使用多个节点来对大量查询进行负载均衡。这项技术在本章的随后部分讲解。
安装
MariaDB galera cluster包含在MariaDB的官方仓库中。也可以从官网下载通用二进制包。支持DEB和YUM工具安装方式:debian,ubuntu,fedora,redhat和centos之类都可以。同样也可以进行源码安装。
需要安装的应用包有:
1 MariaDB-galera-server :MariaDB galera cluster主程序
2 galera :wsrep 提供者
一些依赖包会被自动安装。如果已经安装了MariaDB-server 包,则它会被自动移除。
MariaDB galera cluster 节点间通过非标准端口通信。默认使用以下几个端口
1 4567
2 4568
3 4444
4 3306(标准端口)
如果安装了SELINUX或者APPARMOR,默认它们会阻碍所有的非标准端口间通信,妨碍集群工作。此时,我们需要设置正确的SELINUX或者APPARMOR策略,或者干脆关闭它们。注意SELINUX会严重影响数据库服务器性能。因此关闭它是个不错的办法。SELINUX在redhat的所有衍生发行版中都是默认是关闭的,APPARMOR在ubuntu中默认是开启的。
关闭SELINUX,执行如下命令:
setenforce 0
关闭APPARMOR运行如下命令:
cd /etc/apparmor.d/disable/
ln -s /etc/apparmor.d/usr/sbin.mysqld
service apparmor restart
都需要root权限
如果有iptables或者其他防火墙安装的话,应该为非标准端口设置访问策略。
到此,基本上我们算是已经准备安装MariaDB galera cluster了。
从节点开始
启动一个MariaDB galera cluster节点就是启动一个MariaDB服务器。因此我们可以调用mysqld或者mysqld_safe脚本。
当第一次启动一个节点的时候,我们应该准备好配置文件,最小的配置文件示例如下:
wsrep_provider=/usr/lib/galera/libgalera_smm.so
default_storage_engine=InnoDB
binlog_format=ROW
innodb_autoinc_lock_mode=2
innodb_doublewrite=0
innodb_support_xa=0
query_cache_size=0
选项含义如下:
1 wsrep_provider选项是最重要的,它会告诉galera wsrep库在哪,具体取决于你的系统。
2 因为不能使用innodb以外的存储引擎,因此最重要的是设置default_storage_engine=innodb
3 binlog_format变量只能设置为row
4 innodb_autoinc_lock_mode只能设置为2
5 不能支持innodb的 doublewrite buffer和XA事物
6 不支持query cache
当启动第一个节点的时候,我们必须指定 --wsrep-new-cluster 选项、因此我们可以这样启动
mysqld --wsrep-new-cluster
如果节点崩溃,它必须重新执行之前的命令,因为它不会重新连接到已存在的集群,因此,我们应该使用mysqld_safe 启动集群。并且该节点必须当做一个新的节点加入到集群中。
在一个节点启动后,需要设置相应的权限。集群中的每个节点都必须允许其他节点以root账户连接到本机以便需要的时候创建数据库的拷贝。 此机制称为节点传输,详细内容将会在本章的“节点传输”一节中讲解。因此。我们可以执行一下命令授权
grant all on *.* to 'root'@'node_hostname';
node_hostname 需要替换为真实的节点名或者ip地址。
集群中的每个节点由URL来定义。为了往集群中添加新节点,我们需要指定至少一个当前正在运行的节点。 虽然一个节点通常是足够的,但更好的做法是写多个节点的地址。比如下面这个例子: 不需要其它信息。新节点即可通过该地址加入galera集群。
在一个节点传输完毕后,我们可以检查集群是否正常运行。galera提供了一些表来诊断同步信息。所有的与galera相关的信息都可以通过 wsrep_ 开头的表进行查看。比如这些: 请注意,上面这个查询是在已启动但未连接到集群的节点上执行的。
下面这四个变量需要着重说明:
1 wsrep_ready:节点是否已经加入集群并等待接收复制条目。
2 wsrep_connected: 节点是否连接到wsrep提供者
3 wsrep_cluster_status:节点是否连接到集群中,如果没有,则该值为Disconnected。
4 wsrep_cluster_size: 集群中的节点数
在新节点可以开始复制从其他节点接收的事件之前,当前的数据必须被复制到新节点。这一步叫做 节点预置或者状态传输,具体在本章的节点预置部分讲解
确定节点URL
如之前所说,如果想启动一个新节点或者崩溃后重启,那么必须指明其他的节点地址。因此,dba需要知道如何来确定节点的地址。
galera URL语法如下 1 gcomm: 生产环境用这个。
2 dummy:用来测试galera的配置。使用这个数据不会进行复制。
地址可以是ip,主机名,也可以指定端口号,默认的是4567,这样写也行: 192.168.96.213:4567
多个地址通过逗号分割。比如 192.168.96.213,192.168.96.217.可以使用多播地址,例如IPv4或IPv6地址(最后一部分为1)来标识子网中的所有主机。
有许多选项可以指定,配置和wsrep_provider_options差不多。随后我们再说。这些设置最好都写入配置文件。
最后举几个URL写法的例子:
、
节点预置(Node provisioning)
关于 provisioning 的翻译方法斟酌好久。参考 http://blog.csdn.net/catharryy/article/details/48661557 得到“预置”这个译法。
节点预置或者说状态传输,会从其他节点拷贝完整的数据备份到新的节点中。备份通常称为快照或状态,以突出它是在精确时间点的一致版本的数据。发送其状态的节点称为donor,接收状态的节点叫做joiner。此操作会发生在新节点加入集群或者某节点意外宕机重启时需要接收最新的数据变更时。
有两种主要的节点预置方法:
1 state snapshot transfer (SST)传输完整的快照
2 incremental state transfer ( IST ) 传输修改的变更
实际上,这些方法就是通过完整和增量备份完成的
State snapshot Transfer
此节点预置方法会被用在当新节点加入集群时,因为新节点没有数据。有两种方法来执行SST:
1 mysqldump:此方法使用mysqldump工具生成可以在其他节点创建数据库和数据的sql语句。此方法比较慢,因为它通常需要大量的网络流量。在状态传输期间,donor端会通过全局锁使得数据处于只读状态。此外,此方法要求joiner节点处于运行状态。如果使用了不同版本的MariaDB,或者不同的数据目录结构,那么必须使用mysqldump。
2 rsync,rsync_wan 和 xtrabackup:同样用来把数据从donor复制到joiner中。此方法速度很快。使用rsync复制文件只需要复制已经被修改过的部分。rsync_wan方法使用了带有delta传输算法的rsync,它可以通过广域网或者低速网络进行数据传输,但是在其他情况下会比较慢。percona xtrabackup使得无需锁表即可复制数据。rsync和xtrabackup已经在第八章讨论过了。rsync比xtrabackup更快,但是它采用了阻塞方法。同时要求 innodb_file_per_table 和innodb_file_format在所有节点中配置相同。需要注意的是,如果使用了这些方法,那么在传输之前joiner节点不能被初始化。
配置 wsrep_sst_method变量为joiner配置需要的SST方法,比如这样
wsrep_sst_method=xtrabackup
//* SST支持可编写脚本的接口。此特性允许我们编写脚本来定制数据传输操作以应对我们特殊的使用情况。这是一项非常有用的特性,但是它超出了本书的内容,有兴趣可以看galera的官方文档,其包含了详细内容 *//
incremental state transfer
节点提交的所有写入集都写入称为Galera Cache(GCache)的特殊缓存。此结构可以加速数据的I/O操作。当一个节点崩溃再重启时, 由其他节点执行的写入集会完全存储在至少一个节点的GCache中。此时,增量状态传输方法可以使新节点更新数据。此方法有两大优势。数据传输比SST快,因为它只发送给joiner最近更改过的部分。不需要对donor端进行锁表。它可以在传输期间继续复制接收到的事件。
脑裂问题:
无论何种集群,都会产生脑裂。为理解脑裂的含义和可能发生的情况,想想一下,由数据库组成的集群分裂成了两份数据中心。 或者,假设其中一个数据中心网络连接被断开。集群现在分裂成了两部分。然而每个节点仍旧可以正常工作,并且客户端依旧可以发送数据到节点中。如果集群没有提前装备好处理这种情况,两个数据中心很可能会持续修改同一数据。当断网节点的网络修复后,很可能在数据层面产生冲突。一个集群必须可以自动地解决这些冲突;这被称为对脑裂问题的乐观方法。如果集群不能解决冲突,那就坏菜了。
Galera采用悲观方法解决脑裂。它使用的技术称为权重法,它是所描述的法定一致性算法的变体(见书《Distributed Systems: Concepts and Design by George Coulouris, Jean Dollimore, Tim Kindberg, and Gordon Blair, Pearson Publication 》)。让我们看它如何工作。
集群中的所有节点都保留集群节点的个数。此计数不断更新,如果有新节点加入,则此值增加, 如果一个节点正常关闭,它会与其他节点通信以表明自己将会离开集群。然而,如果一个节点崩溃或者发生永久网络故障,使得节点还没来得及和其他节点通信就断掉了。那么该节点就会变得不可达。如果一个节点一段时间内不可达,其他节点就认为它再也不会到达了。默认超时时间为5秒, 但是可以通过 evs.suspect_timeout选项修改。本章随后部分我们讲解。
当一些节点检测到另一个节点不可达时, 如果集群中的一半以上的节点仍然可达,那么集群的这部分仍然是主要集群。此时该部分仍然可以继续工作。
如果只有一半或少于原始节点数的一半是可达的,那么集群的这部分就成了非主要集群。节点仍旧可以接收连接并响应客户端发来的请求。但是这些数据库均为只读状态。如之前所说,我们可以通过查询wsrep_cluster_status变量来判断当前节点是否属于主要集群。
//* 如果集群只有两个节点,当连接断开时没有任何一个分裂部分的节点数会大于另一个。此时,所有节点都不属于主要节点并且不可以写入数据 ,这就是为什么说galera集群至少使用三个节点的原因 *//
此算法保证了如果集群裂成两个或更多的分区,只会有其中的一个分区成为主要的。任何时候都不可能出现超过一个以上的分区可以修改数据的情况出现。
然而,我们已经提过galera使用的仲裁算法叫做权重仲裁。也就是说可以给节点预置不同的权重。当一个节点不可达时,galera不需要计算当前分区的数量,相反它会对分区的权重进行计算,统计没个分区节点的权重总和。默认每个节点权重为1, 因此,分区的权重与其数量相同。 可以通过pc.weight 选项来配置不同的权重。许可范围为0-255.如果权重被设置为0,则该节点不会影响到权重统计结果。如果一组节点的权重最大,同时这些节点断开了网络连接,这组节点很可能会变成主要集群。(下面举个例子,实在觉得说得别扭,但是又不影响主体知识。不译了)
如果明确设置了节点的权重,就能保证不会有多个分区成为主集群。
//* 其实galera使用的权重仲裁算法比看上去要复杂得多。为了清楚起见,这里已经简化了算法。可以通过设置pc.ignore_quorum来关闭此算法。(注意enable这个参数是disable算法)。如果开启了pc.ignore_quorum,则脑裂问题就会发生。此时我们就得在没有galera的帮助下来解决冲突问题。比如,如果可以的话,需要覆盖由分区执行的所有变更 *//
galera 仲裁者
仲裁者是一种为解决脑裂问题而出现的特殊节点。 它与群集的其余部分通信,就像它是正常节点一样,但不复制任何数据。它的唯一目的是增加它可以通信的分区的数量——让那个分区变成主要集群。
比如我们只有两个节点,如之前所说,如果其中一个节点崩溃或者网络断开。余下的分区就会变成 non-primary状态。然而,如果我们有仲裁者的话,仲裁者没有崩溃,那么和仲裁者连接没有断开的节点,是由两个节点组成的,因此它就会变成主要集群。
更复杂的例子是当galera集群分裂成两个数据分区,并且每个数据分区都有相同数量的节点。 这个例子涉及到更多的服务器,几乎和前一个相同;如果两个数据中心之间的连接丢失,那么它们中的任何一个都不会变成主要集群。此问题无需添加新节点即可解决。我们可以配置一个仲裁者,它不处在任何两个数据分区中。如果其中的一个丢失了和仲裁者的连接,另一个可以和仲裁者进行通信,那么可以通信的这个数据中分区就会变成主要集群。
如果想使用仲裁者,我们需要使用 galera arbitrator daemon(garbd). 其系统变量和wsrep参数与常规节点的选项相同。唯一例外是仲裁器中缺少复制组中的wsrep参数, 因为它对没有复制任何东西的节点来说没有意义。
为使用garbd,可以在配置文件中写:
garbd -cfg /path/to/garb.cnf --daemon
如果想停止仲裁者,可以kill掉 garbd进程。因为仲裁者不写任何数据,所以没什么事。
配置集群
MariaDB galera cluster节点可以通过MariaDB系统参数和galera特定的参数进行配置。最重要的参数就是 wsrep_provider_options 。可以用来配置wsrep参数,以逗号分割,为动态变量
本节将会涉及最重要的变量和参数,完整的选项和参数列表可以在 MariaDB knowledge base中找到。
galera最重要的系统参数
所有的galera特定的参数都带有 'wsrep_' 前缀。可以通过下面命令列出所有变量。
show variables like 'wsrep%';
下面将会讨论galera中的一些重要的系统参数。
集群的一般设置
下面给我们展示了galera中的一般设置:
1 wsrep_provider: wsrep 库的地址
2 wsrep_cluster_address:填写一个或者更多的集群节点。如之前所说,设置此变量来为集群添加新节点是很有必要的。此变量不是动态的。
3 wsrep_cluster_name:集群的名字,节点会拒绝那些不属于同一集群中的节点连接到此。
4 wsrep_node_address :当前节点地址,此变量非动态。
5 wsrep_node_name :节点名称,默认为本机主机名
6 wsrep_on : 决定节点是否复制数据,可以用来临时暂停一个节点。
性能和可靠性
以下列出的参数可以用来调整集群的性能和可靠性
1 wsrep_data_home_dir :设置当前节点的数据目录
2 wsrep_slave_threads : 决定可以使用多少并发线程来进行复制。但是能不能加速操作还说不准。它对加速节点间同步非常有用。 推荐值最小值为CPU内核数的4倍,但是最优值可能比这个更高。此变量不是动态,因此多次重启节点来测试性能是很有必要的。记住,如果此值设置的比1大,那么 innodb_locks_unsafe_for_binlog 必须设置为1.
3 wsrep_causal_reads :如果该变量为OFF,则会使节点应用写操作数据集更快,并且开始执行新语句的时候无需等待更慢的节点。此为默认行为,但是它会在一段时间内导致一些不一致。设置为ON可以防止不一致出现,但是会增加延迟,默认值为OFF
4 wsrep_max_ws_size_setd: 指定最大写集大小,以bytes为单位,非动态变量
5 wsrep_max_ws_rows: 单个写集中最大的行数,非动态变量。
6 wsrep_retry_autocommit: 可以帮助处理集群间冲突。 它设置为事务由于这冲突而失败时的最大重试次数。非动态变量
7 wsrep_load_data_splitting :可以把LOAD DATA INFILE 分解为多个事物,使得速度更快,但是有些不可靠。
设置SST:
以下设置会影响SST的行为:
1 wsrep_sst_donor: 以逗号分割,表示可以作为donor的节点。必须使用节点名,不能使用ip地址。
2 wsrep_sst_donor_rejects_queries : 当前节点是否拒绝充当其他节点的donor 。默认为OFF,意味着可以成为donor。
3 wsrep_sst_method :使用SST方法
4 wsrep_sst_auth : 仅与使用的SST认证连接到正在运行的服务器有关,当使用xtrabackup或者mysqldump进行传输数据时,此变量应该包含用户名和密码以进行节点间认证,以冒号分割,比如 root:my_password。
galera 中的限制
1 wsrep_replicate_myisam: 决定是否可以复制 myisam表。默认为OFF。
2 wsrep_convert_lock_to_trx : 帮助应用程序从MyISAM到InnoDB过度。如果为ON,则lock tables 和unlock tables语句会隐式转换为 start transaction 和commit。默认未OFF,如果开启此特性,应当确认应用程序是否可以正常工作。
3 wsrep_certify_nonPK : galera会自动为那些没有主键的表添加主键。默认为ON
设置 wsrep 参数
许多wsrep参数都可以通过 wsrep_provider_options 变量进设置,以分号分割,比如这样
set wsrep_provider_options ='option=value;option=value';
和系统变量相同,wsrep选项也有动态的和非动态的。如果选项不是动态的,那么节点必须在选项改变后重启。
几乎所有的选项名都有前缀, 其大致指示选项所指的组件。比如这样
group_name.option_name
其中大多数的参数都需要对wsrep有很深的了解。我们只在这里介绍几个最重要的,详细内容可以去MariaDB knowledge base 中查看。
1 base_host: 节点ip或主机名
2 base_port: 使用复制的端口
3 evs.inactive_timeout : 这确定了在使用权重算法来处理问题之前,节点不可达的超时时间。
4 evs.user_send_window :决定多少包可以一起被复制。如果网络很慢,应该增加此值,此选项动态。
5 gcache.dir :GCache文件的路径,可以把GCache 文件写入到不同的存储设备以优化性能。
6 gcache.mem_size : GCache的最大值,非动态
7 gcache.page_size: GCache的页大小。非动态
8 gcs.fc_master_slave: 如果集群中只有一个节点用来做主,可以把这个值设置为ON,意味着所有的客户端仅仅会连接到该节点,非动态。
9 gcs.max_throttle:决定了节点预置是否会被加速,但是如果加速预置则会减慢常规复制操作,该值越低,状态传输速度越快,如果设置为0,则状态传输完成之前,复制将会完全停止。在galera中,减慢一个节点的复制也意味着拖慢集群中所有节点的速度。
10 pc.ignore_quorum :设置为1可以关闭权重算法。除非你知道怎么解决脑裂,否则最好不要这么干。
监控和排错
galera提供了一些状态变量以供监控每个节点的状态。和galera配置参数相同,每个状态变量都以wsrep_ 前缀为开头。可以通过如下命令查看这些状态变量:
show status like 'wsrep%';
此处至少有两种办法来监控MariaDB galera cluster。
1 使用 galer cluster nagios插件,它由 FromDual开发和维护。可以通过 http://www.fromdual.com/ 进行下载。本书不涉及此内容,有兴趣可以去它的官网上看。
2 使用galera 自动通知
通知脚本
可以通过使用wsrep_notify_cmd 来设置shell命令或者脚本。当节点状态改变时会自动触发脚本。可以添加一些额外的参数以在通知运维人员时提供更详细的信息。此脚本可以利用这些信息做出相应的反馈,比如把事件写到文件,或者MariaDB表或者给管理员发送数据库的主要问题的描述。
通知脚本语法如下: 解释如下:
1 command :wsrep_notify_cmd中特定的命令
2 new_status :当前节点状态
3 state_uuid :最新状态变更相关的UUID
4 --primary :此参数指示节点是否是主群集的成员
5 members_list : 逗号分割,连接到集群分区的节点列表。
下面是一个不是很有用,但是很具体地展示了如何写一个通知脚本的例子。以下脚本为bash脚本。如果 node1 变成非主要节点时会给dba发送邮件,脚本如下: 为了让galera调用此脚本,可以设置如下参数
set @@global.wsrep_notify_cmd ='/path/to/notify.sh';
检查状态变量
可以定期执行几个检查来验证集群的完整性,这些检查分为四类
1 cluster health:通过检查集群健康状态,可以知道集群是否被分割或者是否所有的节点都正常运行
2 individual node health: 通过独立的节点健康状态检查,我们可以知道被检查节点是否正在运行,如果不是,可以找出具体的原因
3 replication health: 即便所有的节点都正常运行,我们仍想要确认一下复制的延迟是否在可接受范围之内。
4 network perfirmance: 网络通信的速度
集群的健康状态:
第一个需要检查的集群状态变量就是 wsrep_cluster_size 。如果该值小于真实的节点数,要么至少有一个节点挂掉要么就是集群产生了分裂。在一个节点上检查该变量就足够了。但是如果发现该值实在是太低了,那么可以通过检查其他节点来分析具体的原因。
在这种情况下,我们可以通过检查每个节点的 wsrep_cluster_state_uuid 和wsrep_cluster_conf_id 两个变量。这些值都是UUID,在正常条件下,所有节点都是相同的,如果两个节点有不同的 wsrep_cluster_state_uuid,说明它们不属于同一集群。如果这些值相等,但是wsrep_cluster_conf 不同,说明集群被分裂了。
如果集群被分裂,我们需要连接到每个分裂集群中的节点去确认当前节点是否是主要节点。我们可以通过检查每个节点的wsrep_cluster_status状态变量来判断。如果至少有一个节点不属于主要分区,则集群被分裂了。
独立节点的健康状态
如果集群的规模很小,我们可以检查每个节点来找出运行不正常的节点。
确认当前节点是否运行的最佳办法就是查询wsrep_ready变量。如果节点处于健康状态,该值一定为true。如果不是,那么我们必须找出具体原因。
wsrep_connected 状态变量代表了wsrep库是否正产运行以及是否连接到了MariaDB。也就是说,很可能因为配置错误而导致wsrep没有被加载。此时,我们需要检查像wsrep_cluster_address这样的配置参数。
如果wsrep_connected 为true。我们可以检查wsrep_local_state_comment 变量,如果它为'Joining','Waiting for sst'或者 'Joined' 说明节点仍旧连接到了集群。对于庞大的数据库或者低速网络,‘Waiting for SST’会花费很多时间。
复制的健康状态
在同步复制环境中,集群不会比最慢的节点更快。这是因为集群运行一个事物,就不得不等待所有的节点都运行完毕。因此,经常检查wsrep_flow_control_paused变量就很有必要。该变量值范围为0到1,表示当前节点花费在等待其他节点完成事物的时间。如果该值不是很接近0,则说明我们有延迟问题。
此时,我们可以通过如下两个变量来找出运行缓慢的节点:
1 wsrep_flow_control_sent
2 wsrep_local_recv_queue_avg
运行速度慢的节点这两个变量的值都比较大。
如果某个节点很慢,可以尝试增加复制进程数。当然也不要忘了检查MariaDB的常规性能问题,比如说占用率不正常的 buffer pool
网络性能
如果没有节点比其他节点明显更慢并且我们仍然对性能不满意时,那么整体的性能瓶颈可能就出现在网速上。如果想确认是否是该原因,可以检查wsrep_local_send_queue_avg 状态变量。低速网络会导致产生大量被队列的消息。ping工具就可以简单地确认网络性能优劣。或者使用iftop来给我们展示网络流量,来确认带宽是否被占满。
网速低的原因有很多,但是该主题在本书中不会涉及,此处只提及几个对网络性能有益的建议
1 galera集群需要单独的网络环境。多余的通信会导致复制变慢
2 检查系统防火墙配置,最好关闭
3 如果原因很难定位,可以考虑一下物理层的原因,有时我们可能会发现一条长的电缆被卷起来,或者它靠近磁源。电磁干扰可能会减慢网络速度或使其不可靠。
负载均衡
负载均衡可以应用于任何计算机集群的优化包括平衡节点之间的请求,以使它们都具有大致相同的工作量。客户端连接到负载均衡器,它可以像代理一样重新定位会话。负载均衡器开源,非开源都有。大多数是通用的均衡器,旨在与任何通信协同工作。因此我们可以用在web服务器,文件服务器等等任何服务器中。但并不是所有的负载均衡器都与数据库服务器配合得很好。
在本书中,我们将着重讲解一下专为galera cluster集群设计的负载均衡器—— galera load balancer
Galera Cluster的局限性
MariaDB galera cluster 因为设计原理导致会在应用上有些局限性。开发人员和dba应该明白有些在MariaDB上可以使用的特性在galera中是不可用的。
首先,如之前所说galera只能应用在Linux系统中。
galera目前仅支持innodb存储引擎。实验环境下myisam也是支持的,但是默认关闭。可以通过把 wsrep_replicate_myisam 设置为1开启。galera团队不鼓励在生产环境修改该变量。其他存储引擎在galera中不可取。
获取显式锁的语句,像 select ..for update, select ...lock in share mode, lock tables 或者 flush tables .. for exporty 都是不支持的。这使得MyISAM的使用更不理想。 SERIALIZABLE隔离级别会使得所有简单的查询转换为锁定查询,同样也是不支持的。原因是读操作和锁不会通过集群传播。所以这些锁只能在一个节点上获得,会造成一些潜在的冲突。但是有个例外:如果所有的客户端都连接到同一个节点,那么lock是没有问题的。
galera被设计用来复制那些有主键的表,因此对于一些没有主键的表会产生各种问题,比如delete不支持,XA事物也不支持,innodb 双写缓冲会被关闭,并且query cache也不支持。
在MySQL 数据库中显式修改一张表是不支持的。除此之外,general query log 和error log也只能写到文件中。
(本小节在百度中很难找到具体说明,尤其是对不能LOCK的原因没有合适的解释,只是列出了galera的限制。结合百度的内容和本小节的知识,总结如下:1 由于lock无法传播,所以与锁有关的内容都需要格外注意 2 如果仅有一个节点用来处理连接,那么限制会少得多)
galera load balancer 负载均衡
galera load balancer 是FromDual开发的第三方插件,官网如下
http://www.fromdual.com/
和galera 一样GLB也只能运行在Linux中。
它由一个叫做glbd的程序组成
GLB没有客户端管理程序,如果想发送管理命令,比如添加删除节点,可以使用nc工具。nc工具与tcp进行通信并在屏幕中打印反馈结果,发送给glbd的通用语法如下: host_address 变量可以是运行glbd的主机名或者是ip地址,通常是127.0.0.1. port 为glbd的监听端口。我们可以在启动glbd的时候指定端口号。
启动glbd的语法如下 如果该服务器有多个ip地址,则port处需要写明具体的ip地址和端口号,比如这样:192.168.0.123:8000
node_list处填写集群节点,以逗号隔开。每个节点可以通过这样的方式指定 address:port:weight. 权重是glbd中的一个重要概念,但是它仅仅在通过--top 或者 --single 参数启动时使用,或者如果所使用的策略(下面描述)考虑节点的权重。通过 --top 选项,使得如果其中至少有一个正在运行,那么将始终使用具有较高权重的那个节点。如果所有节点权重相同,则该选项无效。使用 --single 选项,那么仅使用权重最高的节点直到该节点宕机。如果某些服务器在具有低资源的机器上运行,那么节点的权重就是一个很有用的功能,并且应该仅用于复制,除非其他节点崩溃。
glbd支持标准信息选项。比如 --help(打印帮助信息)和--version(输出版本号)。--verbose可以在屏幕中打印更多的信息。
列举一些比较重要的选项:
1 --daemon 使glbd作为一个daemon运行
2 --control <port > 指定通过哪个端口来接受nc发出的管理命令
3 --discovery 当有新节点加入集群时,自动发现并添加。
4 --top和--single 引导glbd根据节点权重来重定位负载。
5 --max_con <number> 设置允许的最大连接数以避免集群超过负载。即便不指定该选项,操作系统本身也会限制。
6 --threads <number>指定使用的线程数,默认为1
7 正常情况下,glbd通过合并小的数据包来优化网络。可以通过--nodelay 选项来关闭该机制。
除非使用--single选项,否则我们通常需要让glbd决定使用哪种策略来指定sql语句执行的目的地。以下是几种可选的策略。
1 如果不指明其他策略,默认使用最少连接数。它会把每个连接重定向到当前连接数最小的节点上。节点的权重也会考虑在内,因此会有负载重的节点比轻的节点接收更多连接的情况出现。
2 --round选项,glbd轮训集群节点。当新的连接到达时,它会重定向到当前节点,并且指针前进指向下一节点或返回到列表中的第一个节点。
3 --random 每个连接会被随机重定向到集群中的节点
4 --source 每个客户端会关联到不同的节点,所有来自同一客户端的连接会被重定向到同一台节点,除非该节点宕机。
glbd使用案例如下:
glbd --daemon --control 8765 --thread 4 3306 host1:4567:1 host2:4567:1 host3:4567:1
本例中,daemon将会为每个连接监听3306端口(MySQL,MariaDB的标准端口),8765端口用来接收管理命令。我们有三个权重一样的主机并且使用了标准策略。glbd使用4个并发线程。
glbd --daemon --single 3306 host1:4567:3 host2:4567:2 host3:4567:1
本例中,glbd没有指定管理端口,因此不能在运行时修改节点列表。有三个不同权重的节点,但是只有host1会被用来接收sql。如果host1宕机,则有host2 接手,以此类推。
下面我们使用nc命令来添加一个新节点
echo "host4:4567:1" | nc -q 127.0.0.1 8765
然后是删除一个节点
echo "host2:4567:-1" | nc -q 127.0.0.1 8765
通过给host2设置-1的权重来从节点列表中删除一个节点,因此host2再也不会被使用。
我们可以通过使用nc来获取glbd的统计数据,比如这样 总结:
本章我们讨论了如何安装配置和管理MariaDB服务器。
讨论了安装,配置以及启动单独的节点来把它们连接到一起。学习了使用galera 仲裁器来处理脑裂问题
讨论了如何监控集群以及找出性能问题
尽管MariaDB galera cluster和单节点的MariaDB服务器非常类似,但仍旧有很多限制。比如,我们知道需要避免在galera中使用 query cache或者XA事物
最后,我们学习了galera load balancer——用以分散节点的负载。
本书中,我们涵盖了有关MariaDB优化,管理,安装等几个高级主题。现在我们已经掌握了解决在服务器活动期间可能出现的所有常见问题的一些必要知识。比如说实施备份计划或者配置复制环境或者集群。当然,这些知识远远不够。读者自身需要理论联系实际,多操作。这可能是最难完成的部分,但是通过这本书和在线文档,我们可以不时地找到我们所需要的信息。
Mastering MariaDB 神秘的MariaDB 中文翻译版的更多相关文章
- SWFUpload 2.5.0版 官方说明文档 中文翻译版
原文地址:http://www.cnblogs.com/youring2/archive/2012/07/13/2590010.html#setFileUploadLimit SWFUpload v2 ...
- redis命令参考和redis文档中文翻译版
找到了一份redis的中文翻译文档,觉得适合学习和查阅.这份文档翻译的真的很良心啊,他是<Redis 设计与实现>一书的作者黄健宏翻译的. 地址:http://redisdoc.com/i ...
- 基于R语言的结构方程:lavaan简明教程 [中文翻译版]
lavaan简明教程 [中文翻译版] 译者注:此文档原作者为比利时Ghent大学的Yves Rosseel博士,lavaan亦为其开发,完全开源.免费.我在学习的时候顺手翻译了一下,向Yves的开源精 ...
- tcpdf开发文档(中文翻译版)
2017年5月3日15:06:15 这个是英文翻译版,我看过作者的文档其实不太友善或者不方便阅读,不如wiki方便 后面补充一些,结构性文档翻译 这是一部官方网站文档,剩余大部分都是开发的时候和网络总 ...
- xdebug所有相关方法函数详解(中文翻译版)
此次翻译部分借助google翻译,如有错误,请联系qq:903464207反馈问题,或者留言反馈 翻译时间:2016年4月18日09:41:34 xdebug.remote_enable = onxd ...
- The Swift Programming Language 中文翻译版(个人翻新随时跟新)
The Swift Programming Language --lkvt 本人在2014年6月3日(北京时间)凌晨起来通过网络观看2014年WWDC 苹果公司的发布会有iOS8以及OS X 10.1 ...
- Awesome Javascript(中文翻译版)
[导读]:GitHub 上有一个 Awesome – XXX 系列的资源整理.awesome-javascript 是 sorrycc 发起维护的 JS 资源列表,内容包括:包管理器.加载器.测试框架 ...
- 萝卜德森的sublime笔记中文翻译版
我已经使用subliem编辑器版本2接近2个月了,并且我在其中找到了一堆有用的技巧.我发觉应该写下这些技巧,为那些对此感兴趣的人们.我会尽力的详细描述,那些看起来像魔法一样的东西,因为很多非常“酷”的 ...
- php.ini中文翻译版--转载
;;;;;;;; ; 警告 ; ;;;;;;;;;;; ; 此配置文件是对于新安装的PHP的默认设置. ; 默认情况下,PHP使用此配置文件安装 ; 此配置针对开发目的,并且*不是*针对生产环境 ; ...
随机推荐
- 每天一个linux命令(4):mkdir
1.命令简介 mkdir (Make Directory 创建目录): 若指定目录不存在则创建目录.在创建目录时,要求创建目录的用户具有写权限,并应保证新建的目录没有重名. 2.用法 用法:mkdir ...
- HDU 1022.Train Problem I【栈的应用】【8月19】
Train Problem I Problem Description As the new term comes, the Ignatius Train Station is very busy n ...
- 哪些 Python 库让你相见恨晚?【转】
原文链接:https://www.zhihu.com/question/24590883/answer/92420471 原文链接:Python 资源大全 ---------------- 这又是一个 ...
- Python性能分析
Python性能分析 https://www.cnblogs.com/lrysjtu/p/5651816.html https://www.cnblogs.com/cbscan/articles/33 ...
- APP开发的基本流程
一个独立App开发人的自白:做APP就是一场赌局,你要会押注 下面我们就直接来看下APP从业者必知的整个APP开发标准流程. 一般的APP开发及上线流程 步骤如下: 首先,制作一款APP,必须要有相关 ...
- centos6 利用外部的smpt服务器计划任务发送邮件
centos可通过修改配置文件以使用外部SMTP服务器,达到不使用sendmail而用外部的smtp服务器发送邮件的目的, 操作如下: 一.安装mailx与sendmail # yum -y inst ...
- python按行遍历一个大文件,最优的语法应该是什么?
理论上来说,总体上file.readlines()可以(取决于实现)不慢于你自己手动的一次次调用file.readline(),因为前者的循环在C语言层面,而你的循环是在Python语言层面. 但是在 ...
- mysql灾备演练问题
前期写的mysql热备份脚本恢复,还没有正式用到过,但是今天演练灾备恢复,但是遇到几个问题. 测试环境: 搭建mysql,安装xtrabackup vim /etc/yum.repos.d/Perco ...
- 【交换机】交换机RLDP(环路检测&链路检测)功能介绍及配置说明
功能简介RLDP 全称是Rapid Link Detection Protocol,是锐捷网络自主开发的一个用于快速检测以太网链路故障的链路协议.一般的以太网链路检测机制都只是利用物理连接的状态,通过 ...
- [Linux]Linux read/write
Read 默认read是block模式,如果想设置非block默认,则open时候参数添加O_NONBLOCK read block模式下,并非等到Buffer满才返回,而是只要有data avaia ...