[监控]如何实现mysql 进程监控?

最近做项目时,定位一个mysql 进程告警问题,顺带了解了下mysql进程监控的实现,之前也有了解过,只是没有记录,今天特意记录下,其实很简单,大概方法如下:

  • 获取当前实例进程数
$processlist_count = $($mysql_path/mysql -h"host"-P"port" -u"user" -p"password" -e "show processlist" | wc -l)
  • 判断当前进程数是否大于阈值
if [ 当前进程数-gt 阈值 ]
then
#将当前的进程快照打印到log中
利用show full processlist 将当前的进程快照拿出来打印到log中
fi
  • 落地阈值内的mysql快照或是其他有用的log

  • 以上只是个粗浅的进程监控逻辑,可以做的更完善,比如监控sleep进程的个数,监控lock进程的个数,或者把进程告警时的slow log 也打到log中,也可以把告警时的nginx log 剥出来打到log中,亦或是把进程告警时当前机器的cpu, mem的情况也打印到log中,这样就便于监控到告警时web, db等组件的情况

Amoeba for mysql浅谈

最近在做数据库实例切换,接触到amoeba,于是稍加学习了一下,先介绍一下,什么是Amoeba?

Amoeba一共有三个项目产品:

Amoeba for MySQL:

为MySQL提供了一种数据库代理的解决方案,可以实现多台MySQL之间的读写分离,具有负载均衡、高可用性、Query过滤、读写分离、可路由相关的query到目标数据库、可并发请求多台数据库合并结果。 在Amoeba上面你能够完成多数据源的高可用、负载均衡、数据切片的功能。

Amoeba for Aladdin:

类似“Amoeba for MySQL”,不同的是“Amoeba for MySQL”只支持MySQL数据库,“Amoeba for Aladdin”支持多种数据库,同时还可以在后台使用多种混合数据库。

Amoeba for MongoDB:

实现对NoSQL数据库MongoDB的代理功能,具备心跳检测、负载均衡、故障转移、查询聚合等功能。

从目前Amoeba的系列产品来看,都是针对数据库存储应用在分布式群集负载场景下的解决方案,覆盖了常用的数据库种类。

Amoeba(变形虫)项目,该开源框架于2008年 开始发布一款 Amoeba for Mysql软件。 着重介绍下Amoeba for mysql, 这个软件致力于MySQL的分布式数据库前端代理层,它主要在应用层访问MySQL的时候充当SQL路由功能,专注于分布式数据库代理层(Database Proxy)开发。座落与 Client、DB Server(s)之间,对客户端透明。具有负载均衡、高可用性、SQL 过滤、读写分离、可路由相关的到目标数据库、可并发请求多台数据库合并结果。 通过Amoeba你能够完成多数据源的高可用、负载均衡、数据切片的功能,目前Amoeba已在很多 企业的生产线上面使用。

从上面这段话可以看出:Amoeba for mysql 主要是mysql的一个代理,主要是应用层访问mysql时充当sql路由。

大致查了一下,amoeba大概从2012年下旬之后,就不再更新了,是一个非常老的开源项目,并且资料比较少,出于现有项目有使用到这个代理,只能硬着头皮啃了.

  • Amoeba安装

详见这篇文档:http://wiki.hexnova.com/pages/viewpage.action?pageId=2031909

  1. 安装Java, Amoeba框架是基于Java SE1.6开发的,建议使用Java SE 1.6版本。java -version
    java version “1.6.0_18”
  2. http://sourceforge.net/projects/amoeba/files/ Amoeba源码下载
  3. 下载后存放并解压到安装的位置。这里假设你将存档文件解压到/usr/local/amoeba-mysql-binary-2.0.1-BETA,解压到其他路径也是一样的。进入Ameoba运行目录:[]$ cd AMOEBA_HOME=/usr/local/amoeba-mysql-binary-2.0.1-BETA/bin
  4. 验证: amoeba start|stop
  • Amoeba 基础入门

详细参考:http://wiki.hexnova.com/pages/viewpage.action?pageId=2031911

Amoeba基础配置介绍

Amoeba有哪些主要的配置文件?

  • 想象Amoeba作为数据库代理层,它一定会和很多数据库保持通信,因此它必须知道由它代理的数据库如何连接,比如最基础的:主机IP、端口、Amoeba使用的用户名和密码等等。这些信息存储在$AMOEBA_HOME/conf/dbServers.xml中。
  • Amoeba为了完成数据切分提供了完善的切分规则配置,为了了解如何分片数据、如何将数据库返回的数据整合,它必须知道切分规则。与切分规则相关的信息存储在$AMOEBA_HOME/conf/rule.xml中。
  • 当我们书写SQL来操作数据库的时候,常常会用到很多不同的数据库函数,比如:UNIX_TIMESTAMP()、SYSDATE()等等。这些函数如何被Amoeba解析呢?$AMOEBA_HOME/conf/functionMap.xml描述了函数名和函数处理的关系。
  • 对$AMOEBA_HOME/conf/rule.xml进行配置时,会用到一些我们自己定义的函数,比如我们需要对用户ID求HASH值来切分数据,这些函数在$AMOEBA_HOME/conf/ruleFunctionMap.xml中定义。
  • Amoeba可以制定一些可访问以及拒绝访问的主机IP地址,这部分配置在$AMOEBA_HOME/conf/access_list.conf中
  • Amoeba允许用户配置输出日志级别以及方式,配置方法使用log4j的文件格式,文件是$AMOEBA_HOME/conf/log4j.xml。

配置一个db节点, 使用Amoeba做操作转发的步骤:

1. 首先,在$AMOEBA_HOME/conf/dbServers.xml中配置一台数据库,如下:

<?xml version="1.0" encoding="gbk"?>

<!DOCTYPE amoeba:dbServers SYSTEM "dbserver.dtd">
<amoeba:dbServers xmlns:amoeba="http://amoeba.meidusa.com/">

  <!--
   Each dbServer needs to be configured into a Pool,
   If you need to configure multiple dbServer with load balancing that can be simplified by the following configuration:
    add attribute with name virtual = "true" in dbServer, but the configuration does not allow the element with name factoryConfig
    such as 'multiPool' dbServer
  -->

 <dbServer name="abstractServer" abstractive="true">
  <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory">
   <property name="manager">${defaultManager}</property>
   <property name="sendBufferSize">64</property>
   <property name="receiveBufferSize">128</property>

   <!-- mysql port -->
   <property name="port">3306</property>

   <!-- mysql schema -->
   <property name="schema">test</property>

   <!-- mysql user -->
   <property name="user">root</property>

   <!--  mysql password -->
   <property name="password">password</property>

  </factoryConfig>

  <poolConfig class="com.meidusa.amoeba.net.poolable.PoolableObjectPool">
   <property name="maxActive">500</property>
   <property name="maxIdle">500</property>
   <property name="minIdle">10</property>
   <property name="minEvictableIdleTimeMillis">600000</property>
   <property name="timeBetweenEvictionRunsMillis">600000</property>
   <property name="testOnBorrow">true</property>
   <property name="testWhileIdle">true</property>
  </poolConfig>
 </dbServer>

 <dbServer name="server1"  parent="abstractServer">
  <factoryConfig>
   <!-- mysql ip -->
   <property name="ipAddress">127.0.0.1</property>
  </factoryConfig>
 </dbServer>

 <dbServer name="multiPool" virtual="true">
  <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool">
   <!-- Load balancing strategy: 1=ROUNDROBIN , 2=WEIGHTBASED , 3=HA-->
   <property name="loadbalance">1</property>

   <!-- Separated by commas,such as: server1,server2,server1 -->
   <property name="poolNames">server1</property>
  </poolConfig>
 </dbServer>

</amoeba:dbServers>

以下对这个简单的配置文件进行一些分析:
这份dbServers配置文件中,我们定义了三个dbServer元素,这是第一个dbServer元素的定义。这个名为abstractServer的dbServer,其abstractive属性为true,这意味着这是一个抽象的dbServer定义,可以由其他dbServer定义拓展。
在第一个dbServer元素分别定义MySQL的端口号、数据库名、用户名以及密码。
manager定义了该dbServer选择的连接管理器(ConnectionManager),这里引用了amoeba.xml的配置,稍后介绍。
dbServer下有poolConfig的元素,这个元素的属性主要配置了与数据库的连接池,与此相关的具体配置会在后面的章节中详细介绍。
命名为server1的dbServer元素,正如你设想的那样,这个server1是abstractServer的拓展,parent属性配置了拓展的抽象dbServer,它拓展了abstractServer的ipAddress属性来指名数据库的IP地址,而在端口、用户名密码、连接池配置等属性沿用了abstractServer的配置。
server1拓展了abstractServer的ipAddress属性。
这一段其实并不需要配置,并不会影响到基本使用。以下大致介绍下此配置的含义:multiPool是一个虚拟的数据库节点,可以将这个节点配置成好几台数据库组成的数据库池。比如上面这个配置中仅配置了一台server1,负载均衡策略为ROUNDROBIN(轮询)。与虚拟数据库节点相关的详细教程会在后面的章节中介绍。
由此,你大概可以理解定义abstractServer的原因:当我们有一个数据库集群需要管理,这个数据库集群中节点的大部分信息可能是相同的,比如:端口号、用户名、密码等等。因此通过归纳这些共性定义出的abstractServer极大地简化了dbServers配置文件

2.配置amoeba.xml

<?xml version="1.0" encoding="gbk"?>

<!DOCTYPE amoeba:configuration SYSTEM "amoeba.dtd">
<amoeba:configuration xmlns:amoeba="http://amoeba.meidusa.com/">
        <proxy>
                <!-- service class must implements com.meidusa.amoeba.service.Service -->
                <service name="Amoeba for Mysql" class="com.meidusa.amoeba.net.ServerableConnectionManager">
                        <!-- amoeba对外连接数据库时的端口 -->
                        <property name="port">8066</property>
                        <!-- amoeba对外连接数据库时的IP -->
                        <property name="ipAddress">127.0.0.1</property>
                        <property name="manager">${clientConnectioneManager}</property>
                        <property name="connectionFactory">
                                <bean class="com.meidusa.amoeba.mysql.net.MysqlClientConnectionFactory">
                                        <property name="sendBufferSize">128</property>
                                        <property name="receiveBufferSize">64</property>
                                </bean>
                        </property>
                        <property name="authenticator">
                                <bean class="com.meidusa.amoeba.mysql.server.MysqlClientAuthenticator">
										<!-- amoeba对外连接数据库时的用户名 -->
                                        <property name="user">root</property>
										<!-- amoeba对外连接数据库时的密码 -->
                                        <property name="password"></property>
                                        <property name="filter">
                                                <bean class="com.meidusa.amoeba.server.IPAccessController">
                                                        <property name="ipFile">${amoeba.home}/conf/access_list.conf</property>
                                                </bean>
                                        </property>
                                </bean>
                        </property>

                </service>
                <!-- server class must implements com.meidusa.amoeba.service.Service -->
                <service name="Amoeba Monitor Server" class="com.meidusa.amoeba.monitor.MonitorServer">
                        <!-- port -->
                        <!--  default value: random number
                        <property name="port">9066</property>
                        -->
                        <!-- bind ipAddress -->
                        <property name="ipAddress">127.0.0.1</property>
                        <property name="daemon">true</property>
                        <property name="manager">${clientConnectioneManager}</property>
                        <property name="connectionFactory">
                                <bean class="com.meidusa.amoeba.monitor.net.MonitorClientConnectionFactory"></bean>
                        </property>

                </service>
                <runtime class="com.meidusa.amoeba.mysql.context.MysqlRuntimeContext">
                        <!-- proxy server net IO Read thread size -->
                        <property name="readThreadPoolSize">20</property>
                        <!-- proxy server client process thread size -->
                        <property name="clientSideThreadPoolSize">30</property>
                        <!-- mysql server data packet process thread size -->
                        <property name="serverSideThreadPoolSize">30</property>
                        <!-- per connection cache prepared statement size  -->
                        <property name="statementCacheSize">500</property>
                        <!-- query timeout( default: 60 second , TimeUnit:second) -->
                        <property name="queryTimeout">60</property>
                </runtime>

        </proxy>
        <!-- 
                Each ConnectionManager will start as thread
                manager responsible for the Connection IO read , Death Detection
        -->
        <connectionManagerList>
                <connectionManager name="clientConnectioneManager" class="com.meidusa.amoeba.net.MultiConnectionManagerWrapper">
                        <property name="subManagerClassName">com.meidusa.amoeba.net.ConnectionManager</property>
                        <!-- 
                          default value is avaliable Processors 
                        <property name="processors">5</property>
                         -->
                </connectionManager>
                <connectionManager name="defaultManager" class="com.meidusa.amoeba.net.MultiConnectionManagerWrapper">
                        <property name="subManagerClassName">com.meidusa.amoeba.net.AuthingableConnectionManager</property>

                        <!-- 
                          default value is avaliable Processors 
                        <property name="processors">5</property>
                         -->
                </connectionManager>
        </connectionManagerList>
        <!-- default using file loader -->
        <dbServerLoader class="com.meidusa.amoeba.context.DBServerConfigFileLoader">
                <property name="configFile">${amoeba.home}/conf/dbServers.xml</property>
        </dbServerLoader>

        <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter">
                <property name="ruleLoader">
                        <bean class="com.meidusa.amoeba.route.TableRuleFileLoader">
                                <property name="ruleFile">${amoeba.home}/conf/rule.xml</property>
                                <property name="functionFile">${amoeba.home}/conf/ruleFunctionMap.xml</property>
                        </bean>
                </property>
                <property name="sqlFunctionFile">${amoeba.home}/conf/functionMap.xml</property>
                <property name="LRUMapSize">1500</property>
                <!--amoeba默认连接的server-->
                <property name="defaultPool">master</property>
                <!--允许在master上写数据-->
         <!--
                <property name="writePool">master</property>
                <!--允许在MultiPool上读数据-->
                <property name="readPool">MultiPool</property>
                -->
                <property name="needParse">true</property>
        </queryRouter>
</amoeba:configuration>

port: amoeba 服务器端口号
user: 登录amoeba的用户名
password: 登录amoeba的密码
defaultPool: 设置默认数据库节点,一般是 master 节点
writePool: 设置只读数据库节点
readPool: 设置只读数据库节点

启动:

sh /data/amoeba/bin/shutdown|start

以上就是Amoeba一些简单的用法和配置,项目仅仅是用amoeba作数据库代理上(为了db安全考虑),没有用到主从和高可用。

主要参考文档: (资料实在是太少太不详尽了

https://www.jianshu.com/p/4aec9f682509
https://juejin.im/entry/596d71126fb9a06bbf70177c
http://wiki.hexnova.com/pages/viewpage.action?pageId=2031911
https://www.biaodianfu.com/amoeba.html
https://blog.51cto.com/tianshili/1640092

Mysql 复制(Replication)

复制(replication)功能可以将一个MySQL数据库服务器(主库)中的数据复制到一个或多个 MySQL 数据库服务器(从库)。默认情况下,复制是异步的;从库不需要永久连接以接收来自主库的更新。你可以将其配置为复制所有数据库、复制指定的数据库,甚至还可以配置为复制数据库中指定的表。

异步复制,同步复制,半同步复制的区别:

异步复制:MySQL默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从库上,如果此时,强行将从提升为主,可能导致新主上的数据不完整, 主库将事务 Binlog 事件写入到 Binlog 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上
全同步复制: 指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响
半同步复制: 是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全完成并且提交的反馈,如此,节省了很多时间

复制原理:

在服务器(主库)上执行的所有DDL和DML语句都会被记录到二进制日志中,这些日志由连接到它的服务器(称为从库)提取。它们只是被复制到从库,并被保存为中继日志。这个过程由一个称为 IO 线程的线程负责。还有一个线程叫作SQL线程,它按顺序执行中继日志中的语句。

IO线程:负责提取binlog复制到从库,保存存中继日志(relay log)
SQL线程:顺序执行relay log 中的语句,重放到从库

原理总结:

1.Master 数据库只要发生变化,立马记录到Binary log 日志文件中
2.Slave数据库启动一个I/O thread连接Master数据库,请求Master变化的二进制日志
3.Slave I/O获取到的二进制日志,保存到自己的Relay log 日志文件中。
4.Slave 有一个 SQL thread定时检查Realy log是否变化,变化那么就更新数据

PS:

DML(data manipulation language)
主要是指select , update, insert, delete,就像它的名字一样,这四条命令是用来对数据库里的数据进行操作的语言.

DDL (data definition language)
DDL主要的命令有create, alter, drop等,ddl主要是用在定义或改变表结构、数据类型,表之间的链接和约束等初始化工作.
DCL (Data Control Language)
数据库控制功能,用来设置更改数据库用户或角色权限的语句,包括:grant, deny, revoke等

复制(主从)有哪些优点:

  • 水平解决方案:将负载分散多个从库以提高性能.比如读写分离,在此环境中,所有的写入和更新必须在主库上执行.但是读可能发生在一个或多个从库上.这样做可以提高写入性能,同时对不断增加的从库也能显著加快其读取速度
  • 数据安全性:可以在从库上运行备份服务而不会损坏主库的数据
  • 远程数据分发:可以使用复制为远程服务器站点创建本地数据的副本,无须永久访问主库

复制常见的拓扑形式:

传统复制–主从复制(一主多从)

链式复制:意味着一台服务器从另一台复制,而另一台服务器又从另一台复制.中间的服务器称为中继主库(主库->中继主库->从库),如下图所示:

链式复制

如果想要在两个数据中心之间复制,一般就会使用这种方式.主库(the primary master)及其从库将位于一个数据中心内。辅助主库(中继主库)从另一个数据中心的主库进行复制。另一个数据中心的所有从库都从辅助主库(the secondary master)复制。

主主复制:两个主库互相之间都可以接受写入和复制

主主复制

多源复制:一个从库将从多个主库复制

多源复制

如何设置从库:

1.在主库上启用二进制日志记录。
2.在主库上创建一个复制用户。
3.在从库上设置唯一的server_id。
4.从主库中取得备份。
5.恢复从库上的备份。
6.执行CHANGE MASTER TO命令。
7.开始复制。

具体步骤:

  • 在主库上,启用二进制日志记录并设置server id.
//更改主库my.cnf
server-id=1
binlog-format = mixed
log-bin=mysql-bin
datadir=/var/lib/mysql
innodb_flush_log_at_trx_commit=1
sync_binlog=1
  • 在主库上,创建一个复制用户,从库使用此账号连接到主库:
GRANT REPLICATION SLAVE ON *.* TO replicant@% IDENTIFIED BY '<<choose-a-good-password>>'
  • 在从库上,设置唯一的server_id选项,这个id与你在主库上的不同
mysql>set @@GLOBAL.SERVER_ID = 2;
  • 使用binlog位置创建备份文件,它会影响数据库的性能,但是不会锁表
mysqldump --skip-lock-tables --single-transaction --flush-logs --master-data --databases test01 > ~/dump.sql

//--master-data选项的作用就是将二进制的信息写入到输出文件中,在这里是写入到备份的sql文件中
//--master-data可以了解还原的到从库的备份文件,position和log_file在哪,这样应该就可以不用停机设置主从同步了
  • 检查文件头部并记下MASTER_LOG_FILE和MASTER_LOG_POS的值
head dump.sql -n80 | grep "MASTER_LOG_POS"
  • 修改从库my.cnf
server-id = 2
binlog-format = mixed
log_bin = mysql-bin
relay-log = mysql-relay-bin
log-slave-updates = 1
read-only = 1
  • 导入sql 备份到从库
mysql -u root -p < ~/dump.sql
  • 从库设置主库同步位置信息:
CHANGE MASTER TO MASTER_HOST='192.168.0.20',MASTER_PORT=5306,MASTER_USER='slave',MASTER_PASSWORD='VsaN4Z8mtE', MASTER_LOG_FILE='slave-bin.000034',MASTER_LOG_POS=271;
  • start slave
  • show slave status

如果一切正常,则Last_Error将为空,Slave_IO_State将报告等待主发送事件。

show slave status

复制延迟可以查看Seconds_Behind_Master的值,它代表的是复制的延迟情况。如果它的值为0,则意味着从库与主库同步;如果为非零值,则表示延迟的秒数;如果为NULL,则表示未复制

PS: 主库my.cnf配置文件中还有一些参数可以了解,设置同步的db和不同步的db

replicate-do-db = exampledb //需要同步的数据库
replicate-ignore-db = mysql //不需要同步的数据库
replicate-ignore-db = information_schema //不需要同步的数据库
replicate-ignore-db = performance_schema //不需要同步的数据库

mysql binlog详解

文章内容源于MySQL 8 Cookbook【这本书不错】

二进制日志包含数据库的所有更改记录,包括数据和结构两方面。二进制日志不记录SELECT 或 SHOW 等不修改数据的操作。运行带有二进制日志的服务器会带来轻微的性能影响。二进制日志能保证数据库出故障时数据是安全的。只有完整的事件或事务会被记录或回读。

1.首先第一个问题:为什么应该使用二进制日志?

  • 复制:使用二进制日志,可以把对服务器所做的更改以流式方式传输到另一台服务器上。从(slave)服务器充当镜像副本,也可用于分配负载。接受写入的服务器称为主(master)服务器。
  • 时间点恢复:假设你在星期日的00:00进行了备份,而数据库在星期日的08:00出现故障。使用备份可以恢复到周日00:00的状态;而使用二进制日志可以恢复到周日08:00的状态。

2.启用binlog方法

要启用binlog,必须设置log_bin和server_id并重启服务器

例如log_bin设置为/data/mysql/binlogs/server1,二进制日志存储在/data/mysql/binlogs文件夹中名为server1.000001、server1.000002等日志文件中。每当服务器启动或刷新日志时,或者当前日志的大小达到max_binlog_size时,服务器都会在系列中创建一个新文件。每个二进制日志的位置都在server1.index文件中被维护

启用binlog代码如下:

shell> sudo vim /etc/my.cnf
[mysqld]
log_bin = /data/mysql/binlogs/server1
server_id = 100

如果log_bin不赋予任何值,在这种情况下,二进制日志是在数据目录中创建的。可以使用主机名作为目录名称

设置了log_bin,如果要生效,记得重启mysql

验证是否生效:

mysql> show variables like 'log_bin%';
log_bin                        On
log_bin_basename               /data/mysql/binlogs/server1
log_bin_index                  /data/mysql/binlogs/server1.index
......

执行SHOW BINARY LOGS;或SHOW MASTER LOGS;,以显示服务器的所有二进制日志

执行命令SHOW MASTER STATUS;以获取当前的二进制日志位置:

mysql> show master status;
File                Position
server1.000002       3273

一旦server1.00000x达到max_binlog_size(默认1G),一个新文件server1.00000x+1就会被创建,并被添加到server1.index中。可以动态设置max_binlog_size。

set @@global.max_binlog_size=xxxxxxxx

禁用会话的二进制日志

有些情况下我们不希望将执行语句复制到其他服务器上。为此,可以使用以下命令来禁用该会话的二进制日志:

mysql>set SQL_LOG_BIN = 0;

在这条语句后的所有SQL语句都不会被记录到二进制日志中,不过这仅仅是针对该会话的。要重新启用二进制日志,可以执行以下操作:

mysql>set SQL_LOG_BIN = 1;

移至下一个日志

可以使用FLUSH LOGS命令关闭当前的二进制日志并打开一个新的二进制日志:

mysql>flush logs;

清理二进制日志

随着写入次数的增多,二进制日志会消耗大量空间。如果放任不管,这些写入操作将很快占满磁盘空间,因此清理它们至关重要。1.使用binlog_expire_logs_seconds 和expire_logs_days 设置日志的到期时间。
如果想以天为单位设置到期时间,请设置 expire_logs_days。例如,如果要删除两天之前的所有二进制日志,请SET@@global.expire_logs_days=2。如果将该值设置为0,则禁用设置会自动到期。
如果想以更细的粒度来设置到期时间,可以使用binlog_expire_logs_seconds变量,它能够以秒为单位来设置二进制日志过期时间。
这个变量的效果和 expire_logs_days 的效果是叠加的。例如,如果expire_logs_days是1并且binlog_expire_logs_seconds是43200,那么二进制日志就会每 1.5 天清除一次。这与将binlog_expire_logs_seconds设置为129600、将expire_logs_days设置为0的效果是相同的。在 MySQL 8.0 中,binlog_expire_logs_seconds和expire_logs_days必须设置为0,以禁止自动清除二进制日志。

2.要手动清除日志,请执行PURGE BINARY LOGS TO ‘<file_name>’。例如,有server1.000001、server1.000002、server1.000003和server1.000004文件,如果执行 PURGE BINARY LOGS TO'server1.000004',则从server1.000001 到 server1.000003 的所有文件都会被删除,但文件server1.000004不会被删除:
除了指定某个日志文件,还可以执行命令PURGE BINARY LOGS BEFORE ‘2017-08-03 15:45:00’。除了使用BINARY,还可以使用MASTER。
mysql> PURGE MASTER LOGS TO ‘server1.000004’ 可以实现和之前语句一样的效果。

3.要删除所有二进制日志并再次从头开始,请执行RESET MASTER

binlog的格式

二进制日志可以写成下面三种格式。
1.STATEMENT:记录实际的SQL语句。
2.ROW:记录每行所做的更改。例如,更新语句更新10行,所有10行的更新信息都会被写入日志。而在基于语句的复制中,只有更新语句会被写入日志,默认格式是ROW。
3.MIXED:当需要时,MySQL会从STATEMENT切换到ROW。

有些语句在不同服务器上执行时可能会得到不同的结果。例如,UUID()函数的输出就因服务器而异。这些语句被称为非确定性的语句,基于这些语句的复制是不安全的。在这些情况下,当设置MIXED格式时,MySQL服务器会切换为基于行的格式。

可以使用兼具全局和会话范围作用域的动态变量binlog_format来设置格式。在全局范围进行设置可使所有客户端使用指定的格式:

mysql>set global binlog_format = 'statement';
mysql>set global binlog_format = 'row';

1.Statement:每一条会修改数据的sql都会记录在binlog中。

优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。)

缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。另外mysql 的复制,像一些特定函数功能,slave可与master上要保持一致会有很多相关问题(如sleep()函数, last_insert_id(),以及user-defined functions(udf)会出现问题).

使用以下函数的语句也无法被复制:

  • LOAD_FILE()
  • UUID()
  • USER()
  • FOUND_ROWS()
  • SYSDATE() (除非启动时启用了 –sysdate-is-now 选项)

同时在INSERT …SELECT 会产生比 RBR 更多的行级锁

2.Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。

优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题

缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。

3.Mixedlevel: 是以上两种level的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种.新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句,还是会记录所有行的变更。

从二进制日志中提取语句:

使用mysqlbinlog程序可以从二进制日志中提取内容,并将其应用到其他服务器

实操:使用各种二进制格式执行几条语句。如果把binlog_format设置为GLOBAL级别(全局范围),必须断开并重新连接,以使更改生效。如果想保持连接,请把binlog_format设置为SESSION级别(会话范围)

更改为基于语句(statement)的的复制(SBR):

更改为基于行(Row)的复制(RBR):

更新几行:

改为mixed格式:

更新几行:

mysqlbinlog操作

要显示日志server1.000001的内容,请执行以下操作:

shell> mysqlbinlog /data/mysql/binlogs/server1.000001

你会得到类似下面的输出结果:

#at 206
#170815 12:49:24 server id 200 end log pos 312 CRC32 0x9197bf88 Query
thread_id exec_time=0 error_code=0
BINLOG '
~
~

在第一行中,#at后面的数字表示二进制日志文件中事件的起始位置(文件偏移量)。
第二行包含了语句在服务器上被启用的时间戳。
时间戳后面跟着 server ID、end_log_pos、thread_id、exec_time和error_code。

● server id:产生该事件的服务器的server_id值(在这个例子中为200)。
● end_log_pos:下一个事件的开始位置。● thread_id:指示哪个线程执行了该事件。
● exec_time:在主服务器上,它代表执行事件的时间;在从服务器上,它代表从服务器的最终执行时间与主服务器的开始执行时间之间的差值,这个差值可以作为备份相对于主服务器滞后多少的指标。
● error_code:代表执行事件的结果。零意味着没有错误发生。

回顾:

1.基于语句复制(SBR):我们在基于语句的复制中执行了UPDATE语句,而且在二进制日志中记录了相同的语句。除了保存在服务器上,会话变量也被保存在二进制日志中,以在从库上复制相同的行为:

2.当使用基于行的复制(RBR)时,会以二进制格式对整行(而不是语句)进行保存,而且二进制格式不能读取。此外,你可以观察长度,单个更新语句会生成很多数据。

3.当使用MIXED格式时,UPDATE语句被记录为SQL语句,而INSERT语句以基于行的格式被记录,因为INSERT有非确定性的UUID()函数:

提取的日志可以被传送给MySQL以回放事件。在重放二进制日志时最好使用force选项,这样即使force选项卡在某个点上,执行也不会停止。稍后,你可以查找错误并手动修复数据。

shell>mysqlbinlog /data/mysql/binlogs/server1.000001 | mysql -f -h <remote_host> -u <username> -p

或者也可以先保存到文件中,稍后执行:

shell> mysqlbinlog /data/mysql/binlogs/server1.000001 > server1.binlog_extract
shell> cat server1.binlog_extract | mysql -h <remote_user> -u <username> -p

根据时间和位置进行抽取我们可以通过指定位置从二进制日志中提取部分数据。假设你想做时间点恢复。假如在 2017-08-19 12:18:00 执行了 DROP DATABASE 命令,并且最新的可用备份是在2017-08-19 12:00:00 做的,该备份已经恢复。现在,需要恢复从 12:00:01 至2017-08-19 12:17:00 的数据。请记住,如果提取完整的日志,它还将包含 DROP DATABASE命令,该命令将再次擦除数据。可以通过–start-datetime和–stop-datatime选项来指定提取数据的时间窗口。

shell>mysqlbinlog /data/mysql/binlogs/server1.000001 --start-datetime="xxxx" --stop-datetime="yyyy" > binlog_extract

使用时间窗口的缺点是,你会失去灾难发生那一刻的事务。为避免这种情况,必须在二进制日志中使用事件的文件偏移量。一个连续的备份会保存它已完成备份的所有binlog文件的偏移量。备份恢复后,必须从备份的偏移量中提取binlog。我们将在第7章中详细了解备份。假设备份的偏移量为471,执行DROP DATABASE命令的偏移量为1793。可以使用–start-position和–stop-position选项来提取偏移量之间的日志:

shell>mysqlbinlog /data/mysql/binlogs/server.000001 --start-position=471 --stop-position=1793 > binlog_extract

请确保DROP DATABASE命令在提取的binlog中不再出现。基于数据库进行提取使用–database选项可以过滤特定数据库的事件。如果多次提交,则只有最后一个选项会被考虑。这对于基于行的复制非常有效。但对于基于语句的复制和MIXED,只有当选择默认数据库时才会提供输出。以下命令从employees 数据库中提取事件:

shell>mysqlbinlog /data/mysql/binlogs/server1.000001 --database=employees > binlog_extract

正如mysql 8文档中所解释的,假设二进制日志是通过使用基于语句的日志记录执行这些语句而创建的:

mysql>
INSERT INTO test.t1 (i) VALUES (100);
INSERT INTO db2.t2 (j) VALUES (200);

use test;
INSERT INTO test.t1(i) VALUES (101);
INSERT INTO t1(i) VALUES (102);
INSERT INTO db2.t2(j) VALUES (201);

use db2;
INSERT INTO test.t1(i) VALUES (103);
INSERT INTO db2.t2(j) VALUES (202);
INSERT INTO t2 (j) VALUES (203);

mysqlbinlog –database=test 不输出前两个 INSERT 语句,因为没有默认数据库。

mysqlbinlog –database=test 输出USE test后面的三条INSERT语句,但不是USE db2后面的三条INSERT语句。

因为没有默认数据库,mysqlbinlog –database=db2 不输出前两条INSERT语句。mysqlbinlog –database=db2不会输出USE test后的三条INSERT语句,但会输出在USE db2之后的三条INSERT语句。

提取行事件显示

默认情况下,基于行的复制日志显示为二进制格式。要查看行信息,必须将–verbose或-v选项传递给mysqlbinlog。行事件的二进制格式以注释的伪SQL语句的形式显示,其中的行以###开始。可以看到,单个更新语句被改写为了每行的UPDATE语句。

shell> mysqlbinlog /data/mysql/binlogs/server1.000001 --start-position=660 --stop-position=1298 -v
~
~
# at 660
#170815 13:29:02 server id 200 end_log_pos 722 CRC32 0xe0a2ec74
Table_map: `employees`.`salaries` mapped to number 165
# at 722
........
BINLOG *
二进制内容......
........
### update `employeeds`.`salaries`
### where
### @1=1001
### @2=240468
### @3='xxxxx'
### @4='zzzzzz'
###SET
### @1=1001
### @2=xxxx
.....

如果你只想查看没有二进制行信息的伪 SQL 语句,请指定–base64-output=”decode-rows”以及–verbose:

shell>mysqlbinlog /data/mysql/binlogs/server.00001 --start-position=xxx --stop-position=xxxx --verbose --base64-output="decode-rouws"

重写数据库名称

假设你想将生产服务器上的employees 数据库的二进制日志恢复为开发服务器上的employees_dev,可以使用–rewrite-db=‘from_name-> to_name’选项。这会将所有from_name重写为to_name。

shell>mysqlbinlog /data/mysql/binlogs/server1.000001 --start-position=xxx --stop-position=yyyy --rewrite-db='employees->employees_dev'

在恢复时禁用二进制日志

在恢复二进制日志的过程中,如果你不希望mysqlbinlog进程创建二进制日志,则可以使用–disable-log-bin选项:

shell>mysqlbinlog /data/mysql/binlogs/server1.00001 --start-position=xxx --stop-position=yyyy --disable-log-bin > binlog_restore

可以看到SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0 被写到binlog_restore里,这样可以防止创建binlog

忽略要写入二进制日志的数据库

可以通过在my.cnf中指定–binlog-do-db=db_name选项,来选择将哪些数据库写入二进制日志。要指定多个数据库,就必须使用此选项的多个实例。由于数据库的名字可以包含逗号,因此如果提供逗号分隔列表,则该列表将被视为单个数据库的名字。需要重新启动MySQL服务器才能使更改生效。

迁移二进制日志

由于二进制日志占用越来越多的空间,有时你可能希望更改二进制日志的位置,可以按照以下步骤操作。单独更改 log_bin 是不够的,必须迁移所有二进制日志并在索引文件中更新位置。mysqlbinlogmove工具可以自动执行这些任务,简化你的工作。

具体操作:

先安装MySQL工具集,以使用mysqlbinlogmove脚本

1.systemctl stop mysql
2.mysqlbinlogmove --bin-log-basename=server1 --binlog-dir=/data/mysql/binlogs /binlogs
3.vim /etc/my.cnf
[mysqld]
log_bin=/binlogs
4.systemctl start mysql

将二进制日志从/data/mysql/binlogs更改为/binlogs,则应使用上面命令。如果base name不是默认名称,则必须通过–bin-log-basename 选项设定base name

记一次值班热线数据恢复操作

今天值班遇到个问题,热线帮用户跨项目复制wiki,由于此功能并不能将原项目wiki的树结构也完整的复制过去,从而导致复制的wiki 在目标项目中是平铺的。因此用户想回滚此次热线的操作,于是有了这篇文章的产生

需要回滚(恢复)数据,首先需要了解几个问题:

  • 热线是什么时候进行复制操作的?
  • 源项目是什么?目标项目是什么?
  • 这个操作大概什么时候结束的?

关于上面三个问题的答案:

热线的回复是:

  • 4.23号16:13分操作的
  • 源项目是A, 目标项目是B
  • 操作时长不确定,做完操作,就去做其他事情了,再回来看是19:15。

要解决这个问题,想过两个方案:

  • 去源项目把wiki 的name 全部找出来,然后去目标项目用wiki的name找一把,把同名的删掉
  • 刨binlog ,把这段操作时间内的目标项目insert 的id 找出来,然后一把删除

方案一:介于功能本身的问题,如果wiki名在目标项目已存在,则会生成一条A项目id_为前缀的新wiki名,所以不清楚有多少目标项目本身存在同名的wiki。介于这个原因,暂时不考虑这个方案

方案二:这个方案的主要问题在于要清楚的知道操作的开始时间和结束时间,上面说过开始时间知道,但是不知道结束时间,所以要搞清楚结束时间才行。

  1. 看了下rotate的binlog的文件最新时间, 找出属于目标范围时间段的文件名。
  2. 具体的binlog 查找语句
mysqlbinlog --start-datetime="2020-04-23 16:13:00" --stoptime="2020-04-23 16:15:00" -vv --base64-output=decode-rows mybinlog.007001 | grep -B 6 -A 10 'INSERT INTO `库名`.`wikis`' | grep -B 10 -A 9 '@1=符合目标项目id的wiki id'

PS:@1 是表的id,这个id 本身包含了目标项目信息,所以用@1=xxxx来过滤是否是目标项目的插入操作

结束时间怎么定呢?看了下php.ini的max_execution_time 是30s。并且该复制操作没有使用异步。所以理论上不会很长时间才结束,可以看nginx upstream time,但是经我研究,发现不太准

所以我分别统计了一分钟,两分钟和余下当天时间内的,进行对比,这个对比包括数据量的对比以及数据本身的对比。

对比数据量的作用是:通常一两分钟内,同一个项目不会有很多的插入操作,不会一分钟内有很多人写wiki。(这个是项目经验之谈)

再者,对比数据本身,用提取出来的id去源项目里找,用名称找,可以找到同名的,证明很大可能是复制操作引入的。比如可以用提取出来的最后一条去源项目找找。以确定最后一条是否符合,如果最大时间符合,那么这时间之前的,理论上也应符合,因为时间比较短。

于是发现把–stoptime参数即可,当天这个开始时间点后面的插入操作的,基本热线值班的复制操作,于是在上面的基础上再提取出id即可。假设上面mysqlbinlog 代码产生的log文件是:/tmp/7001_04231612.log

接下来可以再继续刨出id行:

grep '@1=' /tmp/7001_04231612.log > /tmp/wiki_ids_7001_04231612.log

当提取出id之后,查出近w条数据,先做好备份,再删除,通过以上方法,顺利的帮用户恢复到操作之前的数据,打完收工。

Mysql : Lock wait timeout exceeded 解决方案

方案来源于:https://blog.csdn.net/zmx729618/article/details/51259135

上周在写递归遍历比较两棵树的差异的需求:代码在遍历查询后update db的时候遇到一个mysql的报错: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

google了下,发现文章大同小异,但是真正解决我问题的是上面链接中的文章,

结论是:mysql 默认都是自动提交的,可以用select @@autocommit 查看是否为自动提交,如果不是的话, set global autocommit=1;

定位过程:发现实际的数据量并不大,而update的时候,进程状态一直是updating卡住的。

原来autocommit 这个参数配置值为0, 这个设置导致原来的update语句如果没有commit的话,你再重新执行update语句,就会等待锁定,当等待时间过长的时候,就会报ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction的错误。(因为我是递归update,而配置又是autocommit=0, 所以等待锁定)

临时解决方案: 赶紧commit刚才执行的update语句,之后 set global autocommit=1;

最终解决方案:出现该问题大多数是因为出现死锁或者connection/session没有关闭造成的,去检查执行该sql的语句,检查是否有finally{}中关闭连接

解决BLOB, TEXT, GEOMETRY or JSON column ‘description’ can’t have a default value报错问题

背景:今天在跑migration的时候,有一个库建表失败,查询log显示:”BLOB, TEXT, GEOMETRY or JSON column ‘description’ can’t have a default value”,

解释:报错原因:blob,text等字段类型不能设置default ‘xxxx’默认值

好奇不同的实例,同样的sql语句,有的实例可以执行,有的不能.开始以为是5.7的特性,后来dba清空了sql_mode设置就好了.

总结

上述报错的原因在于:

  • MYSQL5.x是不允许BLOB/TEXT类型的字段拥有默认值的
  • 由于mysql是在’strict mode’严格模式下工作的,如果改为非严格模式,上面的语句就可以执行成功

所以解决方案:

  • mysql.配置文件注释sql-mode
  • 重启mysql服务
#sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" 

MYSQL一些常用操作

  • 备份一张表
CREATE TABLE targetTable LIKE sourceTable; 
INSERT INTO targetTable SELECT * FROM sourceTable; 

比较实用
  • 查看某个库总共有多少张表
SELECT COUNT(*) TABLES, table_schema FROM information_schema.TABLES  WHERE table_schema = 'database_name' 
  • 查看某库某表auto_increment的当前值
//查看test_user库user表的auto_increment的值
select auto_increment from information_schema.tables where table_schema='test_user' and table_name='user';
  • 修改某库某表的auto_increment值
//修改test_user库user表的auto_increment值为1000
alter table test_user.user auto_increment=10000; 
  • 给某个字段加auto_increment
ALTER TABLE 表名 MODIFY COLUMN c_id int(11) NULL AUTO_INCREMEN

alter table customers change id id int not null auto_increment primary key;

ALTER TABLE tabelname ADD new_field_id int(5) unsigned default 0 not null auto_increment ,ADD primary key (new_field_id); 
  • MYSQL删除数据库
drop database db_name;
  • 查看库中所有的视图
show table status where comment='view';
  • 查看已有视图的创建语句
show create view VIEW_NAME; 
  • 删除视图
 DROP VIEW [IF EXISTS] view_name; 
  • mysqldump 不想因为Error而中断
mysqldump --force //--force参数
  • tee 将source命令的打印结果输出到指定文件中,便于查年source中的Error
//tee的功能是把你的所有输入和输出都记录到日志文件中去

tee /tmp/xxx.out (xxx.out为log文件的文件名)

source xxx.sql;

// 使用notee命令来关闭日志记录
notee;
  • Mysqlbinlog 查看
/usr/local/mysql/bin/mysqlbinlog --base64-output=DECODE-ROWS -v mybinlog.000042 | less

--base64-output=DECODE-ROWS -v 这个参数是为了base64解码用的,便于看到原始的sql语句
  • 通过mysqlbinlog 和 grep 命令定位binlog 指定操作
mysqlbinlog --base64-output=DECODE-ROWS -v mybinlog.000042 | grep -i -A 10 -B 10 'delete xxx' > result.log //将结果重定向到result.log中

grep -A 后几行
grep -B 前几行

mysql计算数据库容量大小

在Mysql中会有一个默认的数据库:information_schema,里面有一个Tables表记录了所有表的信息。使用该表来看数据库所占空间大小的代码如下:

如果要查某个库占用多少可以用下列命令:

USE information_schema;


比如查看数据库home的大小

select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables where table_schema='home';

查看指定数据库的某个表的大小
比如查看数据库home中 members 表的大小

select concat(round(sum(data_length/1024/1024),2),'MB') as data from tables where table_schema='home' and table_name='members';

Mysqldump使用详解

最近做项目,需要从库中dump指定条件的数据,又一次将mysqldump详细的看了一遍,记录一下,以便下次不会忘记。

/* mysqldump 选项 */
   --no-create-info 取消创建表sql(默认存在)
   --add-drop-table 每个数据表创建之前添加drop数据表语句
   --add-drop-database 每个数据库创建之前添加drop数据库语句
   --comments 附加注释信息。默认为打开
   --triggers 导出触发器。该选项默认启用,用--skip-triggers禁用它
   --complete-insert 使用完整的insert语句(包含列名称)。这么做能提高插入效率,但是可能会受到max_allowed_packet参数的影响而导致插入失败
   --quick 不缓冲查询,直接导出到标准输出。默认为打开状态,使用--skip-quick取消该选项
   --skip-add-locks 在每个表导出之前增加LOCK TABLES并且之后UNLOCK  TABLE。(默认为打开状态,使用--skip-add-locks取消选项)
   --replace 使用REPLACE INTO 取代INSERT INTO.
   --where, -w只转储给定的WHERE条件选择的记录。请注意如果条件包含命令解释符专用空格或字符,一定要将条件引用起来
   --default-character-set=utf8
   --single-transaction 该选项在导出数据之前提交一个BEGIN SQL语句,BEGIN 不会阻塞任何应用程序且能保证导出时数据库的一致性状态。它只适用于多版本存储引擎(它不显示加锁通过判断版本来对比数据),仅InnoDB。本选项和--lock-tables 选项是互斥的,因为LOCK TABLES 会使任何挂起的事务隐含提交。要想导出大表的话,应结合使用--quick 选项

For example:
/usr/local/mysql/bin/mysqldump --set-gtid-purged=OFF  -h'xxxx' -Pyyy -u'user' -p --default-character-set=utf8 --single-transaction --comments=false --no-create-info=true --add-drop-table=false  --triggers=false --complete-insert --quick --skip-add-locks --databases db_name --tables table_name -w'where_condition'>> /tmp/xxxx.sql

mysqldump -w参数不允许条件过长,会报段错误:
https://stackoverflow.com/questions/50904161/mysqldump-segmentation-fault-on-large-where-id-in

解决方案:将in条件的切成多份循环执行dump,比如一个数组,如果是php, 可以用array_chunk切分成多个数组,implode拼接in条件。