搭建keepalived+mysql主从复制高可用

准备工作

  • 完成keepalived的安装
  • 完成docker的安装
  • docker镜像里面自行安装iproute2, vim, iputils-ping(可选)等工具,便于测试
apt-get install iproute2
apt-get install vim
apt-get install iputils-ping

主数据库master

1. 使用docker安装mysql

mkdir -p ~/compose/mysql-master
cd ~/compose/mysql-master

cat docker-compose.yml
version: '2'
services:
  mysql-master:
    image: mysql:5
    restart: always
    container_name: mysql-master
    ports:
      - 3306:3306
    volumes:
      - ./conf.d:/etc/mysql/conf.d
      - /data/docker/mysql-master/data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=123456
networks:
  default:
    external:
      name: service

假如没有把3306端口映射到宿主机,在宿主机上可通过docker-ip:3306来访问。

2. mysql的配置

cat conf.d/lowercase.cnf
[mysqld]
lower_case_table_names = 1
default-time-zone = '+08:00'
character-set-server = utf8
event_scheduler = on
log-bin = mysql-bin
server-id = 1

参数说明:

  • lower_case_table_names 设置不区分大小写
  • default-time-zone 设置时区为东八区
  • character-set-server 修改字符集为utf8
  • log-bin 开启二进制日志
  • server-id 设置server-id

master开启二进制日志后默认记录所有库所有表的操作,可以通过配置来指定只记录指定的数据库甚至指定的表的操作,具体在mysql配置文件的[mysqld]可添加修改如下选项:

# 不同步哪些数据库
binlog-ignore-db = mysql  
binlog-ignore-db = test  
binlog-ignore-db = information_schema  
# 只同步哪些数据库,除此之外,其他不同步
binlog-do-db = mydatabase

3. 启动服务

docker-compose pull && docker-compose up -d

4. 在宿主机连接mysql

说明: 由于我是使用虚拟机安装的字符版Ubuntu系统,所以使用MyCli作为mysql命令行工具来连接mysql。

# 查看master主机的ip地址为192.168.11.188,使用MyCli连接mysql
mycli -h 192.168.11.188 -u root -p 123456

# 给root用户分配远程访问权限:
grant all on *.* to root@'%' identified by "123456";
flush privileges; 

# 查看master状态
mysql [email protected]:(none)> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 586      |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
Time: 0.004s

需要记录主数据库的二进制文件名(mysql-bin.000003)和位置586。

从数据库slave

1. docker-compose.yml

只需要从主数据库的配置名称由mysql-master改为mysql-slave即可。

2. mysql配置

cat conf.d/lowercase.cnf
[mysqld]
lower_case_table_names = 1
default-time-zone = '+08:00'
character-set-server = utf8
event_scheduler = on
server-id= 2

需添加server-id并且与主数据库中不一致

3. 启动服务

docker-compose pull && docker-compose up -d

4. 在宿主机连接mysql

# 查看slave主机的ip地址为192.168.11.186,使用MyCli连接mysql
mycli -h 192.168.11.186 -u root -p 123456
# 给root用户分配远程访问权限,略

# 执行同步SQL语句,参照MASTER配置:
CHANGE MASTER TO MASTER_HOST='192.168.11.188',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=586;

# 启动slave同步进程
start slave;

# 查看slave状态:
show slave statusG;

其中下面两项为YES则表示成功:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

测试主从复制

在MASTER中新建数据库和表,发现数据在SLAVE中已经实时同步过来

Keepalived监控mysql服务

1. master主机上的配置

cat /etc/keepalived/keepalived.conf

vrrp_script chk_mysql_port {     #检测mysql服务是否在运行。有很多方式,比如进程,用脚本检测等等
    script "/opt/chk_mysql.sh"   #这里通过脚本监测
    interval 2                   #脚本执行间隔,每2s检测一次
    weight -5                    #脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -5
    fall 2                    #检测连续2次失败才算确定是真失败。会用weight减少优先级(1-255之间)
    rise 1                    #检测1次成功就算成功。但不修改优先级
}
vrrp_instance VI_1 {
    state MASTER
    interface ens33 #指定虚拟ip的网卡接口,不一定是eth0根据ifconfig确定
    virtual_router_id 51 #路由器标识,MASTER和BACKUP必须是一致的
    priority 100 #定义优先级,数字越大,优先级越高,在同一个vrrp_instance下,MASTER的优先级必须大于BACKUP的优先级。这样MASTER故障恢复后,就可以将VIP资源再次抢回来
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.11.25
    }
    track_script {
       chk_mysql_port
    }
}

需要配置的地方有:script,state,interface,virtual_router_id,priority,virtual_ipaddress等

2. slave主机上的配置

cat /etc/keepalived/keepalived.conf

vrrp_script chk_mysql_port {     
    script "/opt/chk_mysql.sh"
    interval 2
    weight -5
    fall 2
    rise 1
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens33
    virtual_router_id 51
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.11.25
    }
    track_script {
       chk_mysql_port
    }
}

只需要设置state为BACKUP, priority比MASTER低即可。

3. 监测监本的配置

cat /opt/chk_mysql.sh

#!/bin/bash
counter=$(netstat -na|grep "LISTEN"|grep "3306"|wc -l)
if [ "${counter}" -eq 0 ]
then
    /etc/init.d/keepalived stop
else
   echo "running..." >> /opt/keepalived-running-info.log
   sleep 5000
fi

Keepalived监测Mysql测试

先要保证两台服务器的mysql服务正常启动哦~

1. 启动Keepalived

# 在master和slave上执行
sudo /etc/init.d/keepalived start

# 查看脚本是否正常执行
tail -f /opt/keepalived-running-info.log

# 查看master的ip,发现虚拟ip绑定成功
ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:64:35:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.188/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.11.25/32 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe64:3517/64 scope link
       valid_lft forever preferred_lft forever

2. 高可用测试

在任意一台主机执行以下命令测试:

mycli -h 192.168.11.25 -u root -p 123456 #ok
mycli -h 192.168.11.188 -u root -p 123456 #ok
mycli -h 192.168.11.186 -u root -p 123456 #ok

再次查看master主机的ip,发现虚拟ip不见了:

ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:64:35:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.188/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe64:3517/64 scope link
       valid_lft forever preferred_lft forever

此时查看slave主机的ip定, 发现ip漂移情况,虚拟ip自动绑定到到了slave主机上:

ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:f0:00:ad brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.186/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.11.25/32 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fef0:ad/64 scope link
       valid_lft forever preferred_lft forever

此时查看slave主机情况,Slave_IO_Running变成了Connecting:

mycli -h 192.168.11.186 -u root -p 123456
show slave statusG;

Slave_IO_Running | Connecting
Slave_SQL_Running | Yes

继续测试

现在把master重新启动

mycli -h 192.168.11.188 -u root -p 123456 #ok
show master status;
+------------------+----------+--------------+-------------------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB              | Executed_Gtid_Set |
+------------------+----------+--------------+-------------------------------+-------------------+
| mysql-bin.000009 | 154      |              | mysql,test,information_schema |                   |
+------------------+----------+--------------+-------------------------------+-------------------+
1 row in set
Time: 0.004s

会发现信息发生了改变,再次查看slave的状态恢复正常:

Slave_IO_Running | Yes
Slave_SQL_Running | Yes

继续查看master主机发现未绑定vip,vip依然存在于slave所属主机上面。

那么现在把slave停掉试试看:

docker stop mysql-slave

mycli -h 192.168.11.186 -u root -p 123456 #error
mycli -h 192.168.11.25 -u root -p 123456 # error
mycli -h 192.168.11.188 -u root -p 123456 # ok

出现只有master主机的mysql服务能访问的情况,是因为上面测试把两个mysql服务停止,脚本监测不到3306端口执行了/etc/init.d/keepalived stop,所以需要重新启动keepalived :

# master主机
sudo /etc/init.d/keepalived start

# slave主机
docker start mysql-slave
sudo /etc/init.d/keepalived start

这个时候发现vip又重新绑定到master上面,OK,一切正常。

总结

写的可能有些啰嗦,但是每一步的操作和测试又是必要的,只为记录自己的一次学习心得。

如何连接远程mysql数据库

前言

最近买了个滴滴云,在上面装了wordpress,想看到数据库的内容,虽然可以直接用命令行的方式来看,但是实在是不友好,因此想用小海豚在本地连接远程数据库,实现增删改查。

赋予数据库账号远程连接的权限

首先用xshell登陆到vps,进入到数据库命令行:

mysql -u root -p

接着输入密码

执行:

grant all  on 数据库名.* to 用户名@'%' identified by 用户密码;

%代表的是全部IP都可访问。

扩展:

grant用法
    grant 权限 on 数据库.* to 用户名@'登录主机' identified by '密码'
权限:
    常用总结, ALL/ALTER/CREATE/DROP/SELECT/UPDATE/DELETE
数据库:
     *.*                    表示所有库的所有表
     test.*                表示test库的所有表
     test.test_table  表示test库的test_table表     
用户名:
     mysql账户名
登陆主机:
     允许登陆mysql server的客户端ip
     '%'表示所有ip
     'localhost' 表示本机
     '192.168.10.2' 特定IP
密码:
      账户对应的登陆密码

开放VPS的安全组策略

这步很重要,前面我一直连接不上就是因为这个原因,每个VPS厂商都有自己的安全组策略,什么是安全组策略呢,就是开放允许访问的端口,端口不通的话,流量是没办法进出的,那么也就没办法访问了。

那么我们去具体的VPS厂商的网站去更改安全组,开放数据库的端口即可,如果你没特殊设置的话,一般是3306。

mysql数据库导入导出

mysql数据库导入导出整理。

1. 数据库导入

mysql -uroot -p123456 --default-character-set=utf8 liuyj_ctisd < liuyj_ctisd.sql

2. 数据库导出

导出整个数据库

mysqldump -uroot -p123456 liuyj_ctisd > liuyj_ctisd.sql

导出数据库单表

mysqldump -uroot -p123456 liuyj_ctisd log> liuyj_ctisd_log.sql

导出数据库表结构

mysqldump -uroot -p123456 -d --add-drop-table liuyj_ctisd> liuyj_ctisd.sql

-d  导出空表
--add-drop-table 每个数据表创建之前添加drop数据表语句

3. 问题记录

mysqldump在命令行导出时,如果输入密码,提示如下警告信息(已执行成功,只是会提示警告信息)

Warning: Using a password on the command line interface can be insecure.

3.1.修复方法

  • 在命令行导出时,手动输入密码
  • 在/etc/mysql/my.cnf文件最后追加
[mysqldump]
user=user_name
password=password

MySQL运维系列 之 如何快速定位IO瓶颈

摘要: MySQL的瓶颈,一般分为IO密集型和CPU密集型 CPU出问题的情况比较少,最近就遇到过一次比较大的故障,这个话题后面会有一篇专题介绍 今天主要聊聊IO密集型的应用中,我们应该如何快速定位到是谁占用了IO资源比较多 背景 环境1.

MySQL的瓶颈,一般分为IO密集型和CPU密集型

CPU出问题的情况比较少,最近就遇到过一次比较大的故障,这个话题后面会有一篇专题介绍

今天主要聊聊IO密集型的应用中,我们应该如何快速定位到是谁占用了IO资源比较多

背景

  • 环境
1. MySQL 5.7 +
    低版本MySQL这边不再考虑,就像还有使用SAS盘的公司一样,费时费力,MySQL5.7+ 标配
2. InnoDB 存储引擎
3. Centos 6

实战

关于IO的问题,大家能想到的监控工具有哪些

  • iostat
  • dstat
  • iotop

没错,以上都是神器,可以直接用iotop找到占用资源最多的进程

先上一张图

未分类

是的,根据这张图,你能发现的就是MySQL的某个io线程占用了比较多的disk资源,然后呢?

然后,就是去MySQL里面去找,有经验的DBA会去看slow log,或者processlist中去查找相关的sql语句

通常情况下,DBA只会一脸茫然的看到一堆MySQL的query语句,一堆slow log里面去分析,有如大海捞针,定位问题繁琐而低效

如果,你使用的是MySQL5.7+ 版本,那么你就会拥有一件神器(说了好多遍了),可以快速而精准的定位问题

  • 如何快速定位到IO瓶颈消耗在哪里

iotop + threads

dba:lc> select * from performance_schema.threads where thread_os_id=37012G
*************************** 1. row ***************************
          THREAD_ID: 96
               NAME: thread/sql/one_connection
               TYPE: FOREGROUND
     PROCESSLIST_ID: 15
   PROCESSLIST_USER: dba
   PROCESSLIST_HOST: NULL
     PROCESSLIST_DB: sbtest
PROCESSLIST_COMMAND: Query
   PROCESSLIST_TIME: 0
  PROCESSLIST_STATE: query end
   PROCESSLIST_INFO: INSERT INTO sbtest1(k, c, pad) VALUES(25079106, '33858784348-81663287461-16031064329-06006952037-79426243027-69964324491-90950423034-40185804987-62166137368-06259615216', '47186118229-42754
696460-81034599900-41836403072-66805611739'),(24907169, '77074724245-16833049423-38868029911-54850236074-63700733526-39699866447-52646750572-85552352492-59476301007-32196580154', '79013412600-99031855741-696987
96712-65630963686-19653514942'),(24896311, '28403978193-66350947863-03931166713-97714847962-65299790981-39948912629-14070597101-63277652140-34421148430-61801121402', '05239379274-22840441238-37771744512-9234774
1972-52847679847'),(18489383, '89292717216-01584483614-67433536730-45584233994-29817613740-77179131661-10692787267-83942773303-14971155500-36206705010', '55201342831-85536327239-84383935287-06948377235-96437333
726'),(24790463, '99362943588-41160434740-62783664419-16002619743-04761662097-94273988379-52564232648-19738707042-79143532768-89687113917', '09717575620-89781830996-88443720661-19001024583-14971953687'),(2
   PARENT_THREAD_ID: NULL
               ROLE: NULL
       INSTRUMENTED: YES
            HISTORY: YES
    CONNECTION_TYPE: Socket
       THREAD_OS_ID: 37012
1 row in set (0.00 sec)

你看,消耗资源的SQL语句立刻就呈现在你眼前,就是如此高效

好了,以上列出的,还只是全部功能的冰山一角,更多的玩法等待你去解锁。

以上定位的问题也比较的简单,还有一些复杂的IO问题,比如:binlog写入过大、binlog扫描过多、同步线程阻塞、临时表造成的IO过大,等等问题,都可以用此神器一窥究竟

总结

  1. MySQL5.7 默默的提供了非常多的实用工具和新特性,需要DBA们去挖掘和探索。将看似平淡无奇的特性挖掘成黑武器,你才能成为那闪着光芒的Top5 MySQLer

  2. 工欲善其事必先利其器

MySQL root密码忘记,原来还有更优雅的解法!

一直以来,对于MySQL root密码的忘记,以为只有一种解法-skip-grant-tables。

问了下群里的大咖,第一反应也是skip-grant-tables。通过搜索引擎简单搜索了下,无论是百度,抑或Google,只要是用中文搜索,首页都是这种解法。可见这种解法在某种程度上已经占据了使用者的心智。下面具体来看看。

skip-grant-tables的解法

首先,关闭实例

这里,只能通过kill mysqld进程的方式。

注意:不是mysqld_safe进程,也切忌使用kill -9。

# ps -ef |grep mysqld
root      6220  6171  0 08:14 pts/0    00:00:00 /bin/sh bin/mysqld_safe --defaults-file=my.cnf
mysql      6347  6220  0 08:14 pts/0    00:00:01 /usr/local/mysql57/bin/mysqld --defaults-file=my.cnf --basedir=/usr/local/mysql57 --datadir=/usr/local/mysql57/data --plugin-dir=/usr/local/mysql57/lib/plugin --user=mysql --log-error=slowtech.err --pid-file=slowtech.pid --socket=/usr/local/mysql57/data/mysql.sock --port=3307
root      6418  6171  0 08:17 pts/0    00:00:00 grep --color=auto mysqld

# kill 6347

使用–skip-grant-tables参数,重启实例

# bin/mysqld_safe --defaults-file=my.cnf --skip-grant-tables  --skip-networking &

设置了该参数,则实例在启动过程中会跳过权限表的加载,这就意味着任何用户都能登录进来,并进行任何操作,相当不安全。

建议同时添加–skip-networking参数。其会让实例关闭监听端口,自然也就无法建立TCP连接,而只能通过本地socket进行连接。

MySQL8.0就是这么做的,在设置了–skip-grant-tables参数的同时会自动开启–skip-networking。

修改密码

# mysql -S /usr/local/mysql57/data/mysql.sock

mysql> update mysql.user set authentication_string=password('123456') where host='localhost' and user='root';
Query OK, 0 rows affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 1

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

注意:
这里的update语句针对的是MySQL 5.7的操作,如果是在5.6版本,修改的应该是password字段,而不是authentication_string。

update mysql.user set password=password('123456') where host='localhost' and user='root';

而在MySQL 8.0.11版本中,这种方式基本不可行,因为其已移除了PASSWORD()函数及不再支持SET PASSWORD … = PASSWORD (‘auth_string’)语法。

不难发现,这种方式的可移植性实在太差,三个不同的版本,就先后经历了列名的改变,及命令的不可用。

下面,介绍另外一种更通用的做法,还是在skip-grant-tables的基础上。

与上面不同的是,其会先通过flush privileges操作触发权限表的加载,再使用alter user语句修改root用户的密码,如:

# bin/mysql -S /usr/local/mysql57/data/mysql.sock

mysql> alter user 'root'@'localhost' identified by '123';
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql> alter user 'root'@'localhost' identified by '123';
Query OK, 0 rows affected (0.00 sec)

免密码登录进来后,直接执行alter user操作是不行的,因为此时的权限表还没加载。可先通过flush privileges操作触发权限表的加载,再执行alter user操作。
需要注意的是,通过alter user修改密码只适用于MySQL5.7和8.0,如果是MySQL 5.6,此处可写成

update mysql.user set password=password('123456') where host='localhost' and user='root';

最后重启实例

mysql> shutdown;

# bin/mysqld_safe --defaults-file=my.cnf &

需要注意的是,如果在启动的过程中没有指定–skip-networking参数,无需重启实例。但在网上看到的绝大多数方案,都是没有指定该参数,但重启了实例,实在没有必要。

下面对这个方案做个总结:

  1. 如果只添加了–skip-grant-tables,修改完密码后,其实无需重启,执行flush privileges即可。

  2. 从安全角度出发,建议加上–skip-networking。但因其是静态参数,将其剔除掉需要重启实例。

  3. 加上–skip-networking,虽然可以屏蔽掉TCP连接,但对于本地其它用户,只要有socket文件的可读权限,都能无密码登录。还是存在安全隐患。

  4. 不建议通过update的方式修改密码,更通用的其实是alter user。

更优雅的解法

相对于skip-grant-tables方案,我们来看看另外一种更优雅的解法,其只会重启一次,且基本上不存在安全隐患。

首先,依旧是关闭实例

其次,创建一个sql文件

写上密码修改语句

# vim init.sql 
alter user 'root'@'localhost' identified by '123456';

最后,使用–init-file参数,启动实例

# bin/mysqld_safe --defaults-file=my.cnf --init-file=/usr/local/mysql57/init.sql &

实例启动成功后,密码即修改完毕~

如果mysql实例是通过服务脚本来管理的,除了创建sql文件,整个操作可简化为一步。

# service mysqld restart --init-file=/usr/local/mysql57/init.sql 

注意:该操作只适用于/etc/init.d/mysqld这种服务管理方式,不适用于RHEL 7新推出的systemd。

MySQL 内核深度优化

MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务型)、存在多个分支、读写效率瓶颈等问题。

所以如何用好MYSQL变得至关重要,一方面需要通过MYSQL优化找出系统读写瓶颈,提高数据库性能;另一方面需要合理涉及数据结构、调整参数,以提高用户操作响应;同时还有尽可能节省系统资源,以便系统可以提供更大负荷的服务。本文将为大家介绍腾讯云团队是如何对Mysql进行内核级优化的思路和经验。

早期的CDB主要基于开源的Oracle MySQL分支,侧重于优化运维和运营的OSS系统。在腾讯云,因为用户数的不断增加,对CDB for MySQL提出越来越高的要求,腾讯云CDB团队针对用户的需求和业界发展的技术趋势,对CDB for MySQL分支进行深度的定制优化。优化重点围绕内核性能、内核功能和外围OSS系统三个维度展开,具体的做法如下:

一.内核性能的优化

由于腾讯云上的DB基本都需要跨园区灾备的特性,因此CDB for MySQL的优化主要针对主从DB部署在跨园区网络拓扑的前提下,重点去解决真实部署环境下的性能难题。经过分析和调研,我们将优化的思路归纳为:“消除冗余I/O、缩短I/O路径和避免大锁竞争”。以下是内核性能的部分案例:

1.主备DB间的复制优化

未分类

问题分析

如上图所示,在原生MySQL的复制架构中,Master侧通过Dump线程不断发送Binlog事件给Slave的I/O线程,Slave的I/O线程在接受到Binlog事件后,有两个主要的动作:

  • 写入到Relay Log中,这个过程会和Slave SQL线程争抢保护Relay Log的锁。
  • 更新复制元数据(包含Master的位置等信息)。

优化方法

经过分析,我们的优化策略是:

  • Slave I/O线程和Slave SQL线程是典型的单写单读生产者-消费者模型,是可以做到无锁设计的;因此实现思路就是SlaveI/O线程在每次写完数据后,原子更新Relay Log的长度信息,Slave SQL线程读取RelayLog的时以长度信息为边界。这样就将原本竞争激烈的Relay Log锁化解为无锁;
  • 由于Binlog事件中的GTID(Global Transaction Identifier)和DB事务是一一对应的关系,所以RelayLog中的数据本身已经包含了所需要的复制元数据,所以我们可以不写Master info文件,消除了冗余的文件I/O;
  • 于DB都是以事务为更新粒度的,因为在RelayLog文件I/O上,我们通过合并离散小I/O为事务粒度的大I/O等手段,使磁盘I/O得以大幅提升。

优化效果

未分类

如上图所示,经过优化:左图35.79%的锁竞争(futex)已经被完全消除;同压测压力下,56.15%的文件I/O开销被优化到19.16%,Slave I/O线程被优化为预期的I/O密集型线程。

2.主库事务线程和Dump线程间的优化

未分类

问题分析

如上图所示,在原生MySQL中多个事务提交线程TrxN和多个Dump线程之间会同时竞争Binlog文件资源的保护锁,多个事务提交线程对Binlog执行写入,多个Dump线程从Binlog文件读取数据并发送给Slave。所有的线程之间是串行执行的!

优化方法

经过分析,我们的优化策略是:

  • 将读写分离开来,多个写入的线程还是在锁保护下串行执行,每一个写入线程写入完成后更新当前Binlog的长度信息,多个Dump线程以Binlog文件的长度信息为读取边界,多个Dump线程之间并行执行。以这种方式来让复制拓扑中的Dump线程发送得更快!

效果

未分类

经过测试,优化后的内核,不仅提升了事务提交线程的性能,在Dump线程较多的情况下,对主从复制性能有较大提升。

二.主备库交互流程优化

未分类

问题分析

如上图所示,在原生MySQL中主备库之间的数据发送和ACK回应是简单的串行执行,在上一个事件ACK回应到达之前,不允许继续发送下一个事件;这个行为在跨园区(RTT 2-3ms)的情况性能非常差,而且也不能很好地利用带宽优势。

优化方法

经过分析,我们的优化策略是:

  • 将发送和ACK回应的接收独立到不同的线程中,由于发送和接收都是基于TCP流的传输,所以时序性是有保障的;这样发送线程可以在未收ACK之前继续发送,接受线程收到ACK后唤醒等待的线程执行相应的任务。

效果

根据实际用例测试,优化后的TPS提升为15%左右。

三.内核功能的优化

1. 预留运维帐号连接数配额

在腾讯云上,不时遇到用户APP异常或者BUG从而占满DB的最大连接限制,这是CDB OSS帐号无法登录以进行紧急的运维操作。针对这个现状,我们在MySQL内核单独开辟了一个可配置的连接数配额,即便在上述场景下,运维帐号仍然可以连接到DB进行紧急的运维操作。极大地降低了异常情况下DB无政府状态的风险。该帐号仅有数据库运维管理权限,无法获取用户数据,也保证了用户数据的安全性。

2. 主备强同步

针对一些应用对数据的一致性要求非常高,CDB在MySQL原生半同步的基础上进行了深度优化,确保一个事务在主库上提交之前一定已经复制到至少一个备库上。确保主库宕机时数据的一致性。

四.外围系统的优化

除了以上提到的MySQL内核侧的部分优化,我们也在外围OSS平台进行了多处优化。例如使用异步MySQL ping协议实现大量实例的监控、通过分布式技术来加固原有系统的HA/服务发现和自动扩容等功能、在数据安全/故障切换和快速恢复方面也进行了多处优化。

在此我向大家推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

mysql的UNIX_TIMESTAMP用法

mysql的UNIX_TIMESTAMP用法

一、UNIX_TIMESTAMP 一般是用于unix的时间戳。

例子一、日期转化为时间戳

SELECT UNIX_TIMESTAMP("2016-07-11")

结果:
— 1468166400

例子二、日期转化为时间戳

SELECT UNIX_TIMESTAMP("2016-07-17 23:59:59")

结果:

— 1468771199

二、FROM_UNIXTIME:表示把UNIX_TIMESTAMP还原成标准的时间格式

SELECT FROM_UNIXTIME(1468166400),FROM_UNIXTIME(1468771199)

结果:

2016-07-11 00:00:00 2016-07-17 23:59:59

记住,永远不要在MySQL中使用“utf8”

最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误:

Incorrect string value: ‘xF0x9Fx98x83 <…’ for column ‘summary’ at row 1

我用的是UTF-8编码的客户端,服务器也是UTF-8编码的,数据库也是,就连要保存的这个字符串“ <…”也是合法的UTF-8。

问题的症结在于,MySQL的“utf8”实际上不是真正的UTF-8。

“utf8”只支持每个字符最多三个字节,而真正的UTF-8是每个字符最多四个字节。

MySQL一直没有修复这个bug,他们在2010年发布了一个叫作“utf8mb4”的字符集,绕过了这个问题。

当然,他们并没有对新的字符集广而告之(可能是因为这个bug让他们觉得很尴尬),以致于现在网络上仍然在建议开发者使用“utf8”,但这些建议都是错误的。

简单概括如下:

  • MySQL的“utf8mb4”是真正的“UTF-8”。
  • MySQL的“utf8”是一种“专属的编码”,它能够编码的Unicode字符并不多。

我要在这里澄清一下:所有在使用“utf8”的MySQL和MariaDB用户都应该改用“utf8mb4”,永远都不要再使用“utf8”。

那么什么是编码?什么是UTF-8?

我们都知道,计算机使用0和1来存储文本。比如字符“C”被存成“01000011”,那么计算机在显示这个字符时需要经过两个步骤:

  • 计算机读取“01000011”,得到数字67,因为67被编码成“01000011”。
  • 计算机在Unicode字符集中查找67,找到了“C”。

同样的:

  • 我的电脑将“C”映射成Unicode字符集中的67。
  • 我的电脑将67编码成“01000011”,并发送给Web服务器。

几乎所有的网络应用都使用了Unicode字符集,因为没有理由使用其他字符集。

Unicode字符集包含了上百万个字符。最简单的编码是UTF-32,每个字符使用32位。这样做最简单,因为一直以来,计算机将32位视为数字,而计算机最在行的就是处理数字。但问题是,这样太浪费空间了。

UTF-8可以节省空间,在UTF-8中,字符“C”只需要8位,一些不常用的字符,比如“”需要32位。其他的字符可能使用16位或24位。一篇类似本文这样的文章,如果使用UTF-8编码,占用的空间只有UTF-32的四分之一左右。

MySQL的“utf8”字符集与其他程序不兼容,它所谓的“”,可能真的是一坨……

MySQL简史

为什么MySQL开发者会让“utf8”失效?我们或许可以从提交日志中寻找答案。

MySQL从4.1版本开始支持UTF-8,也就是2003年,而今天使用的UTF-8标准(RFC 3629)是随后才出现的。

旧版的UTF-8标准(RFC 2279)最多支持每个字符6个字节。2002年3月28日,MySQL开发者在第一个MySQL 4.1预览版中使用了RFC 2279。

同年9月,他们对MySQL源代码进行了一次调整:“UTF8现在最多只支持3个字节的序列”。

是谁提交了这些代码?他为什么要这样做?这个问题不得而知。在迁移到Git后(MySQL最开始使用的是BitKeeper),MySQL代码库中的很多提交者的名字都丢失了。2003年9月的邮件列表中也找不到可以解释这一变更的线索。

不过我可以试着猜测一下。

2002年,MySQL做出了一个决定:如果用户可以保证数据表的每一行都使用相同的字节数,那么MySQL就可以在性能方面来一个大提升。为此,用户需要将文本列定义为“CHAR”,每个“CHAR”列总是拥有相同数量的字符。如果插入的字符少于定义的数量,MySQL就会在后面填充空格,如果插入的字符超过了定义的数量,后面超出部分会被截断。

MySQL开发者在最开始尝试UTF-8时使用了每个字符6个字节,CHAR(1)使用6个字节,CHAR(2)使用12个字节,并以此类推。

应该说,他们最初的行为才是正确的,可惜这一版本一直没有发布。但是文档上却这么写了,而且广为流传,所有了解UTF-8的人都认同文档里写的东西。

不过很显然,MySQL开发者或厂商担心会有用户做这两件事:

  • 使用CHAR定义列(在现在看来,CHAR已经是老古董了,但在那时,在MySQL中使用CHAR会更快,不过从2005年以后就不是这样子了)。
  • 将CHAR列的编码设置为“utf8”。

我的猜测是MySQL开发者本来想帮助那些希望在空间和速度上双赢的用户,但他们搞砸了“utf8”编码。

所以结果就是没有赢家。那些希望在空间和速度上双赢的用户,当他们在使用“utf8”的CHAR列时,实际上使用的空间比预期的更大,速度也比预期的慢。而想要正确性的用户,当他们使用“utf8”编码时,却无法保存像“”这样的字符。

在这个不合法的字符集发布了之后,MySQL就无法修复它,因为这样需要要求所有用户重新构建他们的数据库。最终,MySQL在2010年重新发布了“utf8mb4”来支持真正的UTF-8。

为什么这件事情会让人如此抓狂

因为这个问题,我整整抓狂了一个礼拜。我被“utf8”愚弄了,花了很多时间才找到这个bug。但我一定不是唯一的一个,网络上几乎所有的文章都把“utf8”当成是真正的UTF-8。

“utf8”只能算是个专有的字符集,它给我们带来了新问题,却一直没有得到解决。

总结

如果你在使用MySQL或MariaDB,不要用“utf8”编码,改用“utf8mb4”。这里(https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4)提供了一个指南用于将现有数据库的字符编码从“utf8”转成“utf8mb4”。

MySQL不支持InnoDB的解决方法

在OpenSUSE下装上MySQL后,发现无法选择添加事务支持数据引擎InnoDB。
G一下后,解决如下:
/var/lib/mysql目录下,删除ibdata1、ib_logfile1、 ib_logfile0,然后重启MySql让其重建以上文件:

mysqladmin -uroot -p shutdown
sudo mysqld_safe &

搞定!
下面是网络上的其它文章。大家也可以参考下。
早上起来,到PHP站点去看了下,准备测试下别人写的一个CMS系统,高兴的下载了程序,然后把程序拷贝到所在目录。由于该程序没有install.php,里面只包含了一个.sql的数据库语句,只得到mysql数据库中去执行这条语句:
进入数据库后,输入source 所在目录/
.sql
这个时候问题出现了:
QUOTE:

MySQL Server Error:
The 'InnoDB' feature is disabled; you need MySQL built with 'InnoDB' to have it working

在mysql中输入SHOW variables like “have_%”查看,显示如下:

mysql> SHOW variables like "have_%"
-> ;
+-----------------------+----------+
| Variable_name | Value |
+-----------------------+----------+
| have_archive | YES |
| have_bdb | NO |
| have_blackhole_engine | NO |
| have_compress | YES |
| have_crypt | NO |
| have_csv | NO |
| have_dynamic_loading | YES |
| have_example_engine | NO |
| have_federated_engine | NO |
| have_geometry | YES |
| have_innodb | DISABLED |
| have_isam | NO |
| have_merge_engine | YES |
| have_ndbcluster | NO |
| have_openssl | DISABLED |
| have_query_cache | YES |
| have_raid | NO |
| have_rtree_keys | YES |
| have_symlink | YES |
+-----------------------+----------+
19 rows in set (0.00 sec)

蓝色表示我的MYSQL并不支持innodb。
MySQL中InnoDB和MyISAM类型的差别

  • InnoDB和MyISAM是在使用MySQL最常用的两个表类型,各有优缺点,视具体应用而定。下面是已知的两者之间的差别,仅供参考。
    1.InnoDB不支持FULLTEXT类型的索引。
    2.InnoDB 中不保存表的具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。
    3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
    4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
    5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
    另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如update table set num=1 where name like “%aaa%”
    任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。
    如果你想使用外键,事务等功能,记得用innodb引擎。使用方法是create table xxx()engine=innodb;如果想所有建立的表格都用innodb引擎,可以把“default-storage-engine=INNODB”加到/etc/mysql/my.cnf(位置可能不同)。设完之后就可以用“show engines;”检查是否设置好。不过据说该设置在5.0.22下可能无效。

网上查找了,打开我的my.ini文件,找到skip-innodb,改成#skip-innodb。
之后重启mysql。。问题解决。

mysql> SHOW variables like "have_%"
-> ;
+-----------------------+----------+
| Variable_name | Value |
+-----------------------+----------+
| have_archive | YES |
| have_bdb | NO |
| have_blackhole_engine | NO |
| have_compress | YES |
| have_crypt | NO |
| have_csv | NO |
| have_dynamic_loading | YES |
| have_example_engine | NO |
| have_federated_engine | NO |
| have_geometry | YES |
| have_innodb | YES |
| have_isam | NO |
| have_merge_engine | YES |
| have_ndbcluster | NO |
| have_openssl | DISABLED |
| have_query_cache | YES |
| have_raid | NO |
| have_rtree_keys | YES |
| have_symlink | YES |
+-----------------------+----------+
19 rows in set (0.00 sec)

MySQL 字符串 转 int/double CAST与CONVERT 函数的用法

MySQL 的CAST()和CONVERT()函数可用来获取一个类型的值,并产生另一个类型的值。两者具体的语法如下:

CAST(value as type);  
CONVERT(value, type);  

就是CAST(xxx AS 类型), CONVERT(xxx,类型)。

mysql> SELECT CAST('3.35' AS signed);  
+------------------------+  
| CAST('3.35' AS signed) |  
+------------------------+  
|                      3 |  
+------------------------+  
1 row in set  

可以转换的类型是有限制的。这个类型可以是以下值其中的一个:

  • 二进制,同带binary前缀的效果 : BINARY
  • 字符型,可带参数 : CHAR()
  • 日期 : DATE
  • 时间: TIME
  • 日期时间型 : DATETIME
  • 浮点数 : DECIMAL
  • 整数 : SIGNED
  • 无符号整数 : UNSIGNED

下面举几个例子:

例一

mysql> SELECT CONVERT('23',SIGNED);  
+----------------------+  
| CONVERT('23',SIGNED) |  
+----------------------+  
|                   23 |  
+----------------------+  
1 row in set  

例二

mysql> SELECT CAST('125e342.83' AS signed);  
+------------------------------+  
| CAST('125e342.83' AS signed) |  
+------------------------------+  
|                          125 |  
+------------------------------+  
1 row in set  

例三

mysql> SELECT CAST('3.35' AS signed);  
+------------------------+  
| CAST('3.35' AS signed) |  
+------------------------+  
|                      3 |  
+------------------------+  
1 row in set  

像上面例子一样,将varchar 转为int 用 cast(a as signed),其中a为varchar类型的字符串。