mysql主从同步快速设置

记录一个比较简便的mysql的主从同步设置步骤,方便日后使用。

安装环境

  • centos 5.4
  • mysql 5.1.xx 采用rpm直接安装
  • xtrabackup 1.2.22 采用rpm直接安装
  • 1. Master:/etc/my.cnf

    [mysqld]
    server-id = 1
    log-bin
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
    datadir=/var/lib/mysql
    
    character-set-server=utf8
    init_connect='SET NAMES utf8'
    

    设定了默认字符集为utf8,可以按实际情况取舍这段配置。

    2. Slave:/etc/my.cnf

    [mysqld]
    server-id=2
    datadir=/var/lib/mysql
    
    character-set-server=utf8
    init_connect='SET NAMES utf8'

    3. Master:在master数据库设置用来同步的slave用户权限

    
    GRANT REPLICATION SLAVE ON *.*
    TO '<slave_username>'@'<slave_ip>' 
    IDENTIFIED BY '<slave_password>';
    

    4. Master:导出数据到slave

    采用xtrabackup来备份mysql,好处是在master的锁表时间很短,在实际的生产环境也可以使用,并且xtrabackup会自动记录同步日志文件的位置。

    
    sudo innobackupex-1.5.1 --stream=tar /tmp/ | ssh <slave_host> "mkdir /tmp/db; tar xfi - -C /tmp/db/"
    

    这个步骤会把master的数据包括表结构整个导出并压缩复制给slave,同时解压到slave的/tmp/db目录下。

    5. Slave:导入数据到slave

    innobackupex-1.5.1 --apply-log /tmp/db
    innobackupex-1.5.1 --copy-back /tmp/db
    chown -R mysql.mysql /var/lib/mysql/*

    6. Slave:开始同步数据

    查看/var/lib/mysql/xtrabackup_binlog_info,获得日志文件以及position。

    
    CHANGE MASTER TO 
    MASTER_HOST='<master_host>', 
    MASTER_USER='<slave_username>', 
    MASTER_PASSWORD='<slave_password>', 
    MASTER_LOG_FILE='<see xtrabackup_binlog_info>', 
    MASTER_LOG_POS=<see xtrabackup_binlog_info>;
    
    START SLAVE;
    

    mysql 5.5.0已经可以下载

    很高兴能够看到这个时候mysql还能出个新版本,mysql 5.5.0是所谓的milestone version,这次带来了一些有趣的东西,按照重要程度(我主观上的)列一下:

    这个版本还不适合用在正式环境。想想以后也许可以用mysql mmm + mysql 5.5提供可用性更好的服务,前提是它已经足够稳定,或者是percona版本?

    mysql的partition与auto_increment

    mysql 5.1之后,开始新增了partition功能,可以做到对代码透明的情况下进行数据分区。但是从实际使用中来看,限制颇多:

      • 如果用来分区的字段和主键不是同一个,则不能分区
      • 见很早前的一篇译文mysql 5.1新功能 -- 按日期分区,如果查询条件中没有正确利用到分区的字段,那么partition的效果为零
      • mysql 5.1.31之前的partition功能有较大的bug,会导致自增字段不能正常自增,怎么个不正常法?auto_increment值会非正常增长,突然比当前的最大id小很多,插入数据时导致duplicate key错误。

    这些问题都能凑合着对付过去,但是partition功能和auto_increment似乎是天生的敌人。从使用习惯上来说,我们会把auto_increment字段设置为主键,这个字段要是unique的才比较稳妥,这个情况下如果希望用其它字段来进行分区的话,几乎就成了不可能的任务。以这张表为例:

    
    CREATE TABLE user (  
      id int auto_increment PRIMARY KEY,
      uid int,
      foo varchar(32)
    ) engine=myisam;
    

    这个时候试图以uid进行分区会怎么样?是以怎样的惨淡结局收场?变通的办法是对自增字段做一些调整,从mysql的手册来看,自增字段不一定要设置成主键,仅仅是普通的index也可以,那么把表结构换成下面这个样子,就可以分区成功。

    
    CREATE TABLE user (  
      id int auto_increment,
      uid int,
      foo varchar(32),
      index (id)
    ) engine=innodb PARTITION BY HASH (uid) PARTITIONS 4;
    

    我在实际环境中有一张表达到了几亿条数据,如果要用时下流行的sharding策略,必须在代码上做一些改动,当时偷了点懒直接使用partition功能,用了大半年倒也相安无事。只是分区表占用内存相当的大,如果有其它的表经常出现慢查询耗费服务器资源,那么雪崩效应就出现了,所有的查询都会堵塞变得非常慢。

    关于mysql proxy 0.7.0

    听说mysql proxy 0.7.0即将发布,正好前些日子从bzr上获取mysql proxy的代码编译过,看看当时bzr的版本号,和现在也差不了多少。在这期间,我又花了一些时间把mysql配置成读写分离,出了不少妖蛾子,由于使用的人不太多,解决问题超费劲——搜索不到有帮助的内容啊。但是我可能是比较幸运的,最后成功的实现了读写分离,目前在开发环境运行的比较稳定,所以有必要做个笔记分享一下。

    读写分离脚本的问题

    刚启动mysql proxy的时候,经常报错 — “Mysql server has gone away”。我进一步缩小了可能出问题的范围(把环境简化是很重要的查错手段哦!),比如只连接一个mysql,或者只连接本机的mysql,没有太大帮助,最后是在mysql proxy的日志文件中看到些蛛丝马迹:

    (critical) proxy-plugin.c:1367: (connect_server) [string “/usr/local/share/mysql-proxy/r…”]:69: .address is deprecated. Use .src.name or .dst.name instead
    (critical) (read_query) [string “/usr/local/share/mysql-proxy/r…”]:179: .address is deprecated. Use .src.name or .dst.name instead
    (critical) proxy-plugin.c.1115: I have no server backend, closing connection

    mysql proxy的论坛上看到有人碰到类似的问题,很简单,读写分离的lua脚本还是旧的,0.6.1时代的产物了,更糟糕的是,即便是即将发布的0.7.0,rw-splitting.lua也是旧版本的。lua脚本中的.address需要替换成.src.name或者.dst.name

    解决办法 —— 下载更新之后的rw-splitting.lua,情况会好转。

    使用prepare方法无法获得结果

    我在测试代码中采用php的pdo_mysql,单独连接mysql是毫无问题的,然而配合mysql proxy使用则是屡屡受挫,查询经常没有结果返回,比较随机,从日志中也找不到有帮助的内容。这次没有找到解决办法,所以我绕了过去,在连接mysql的时候使用伪prepare的方式:

    $option = array(PDO::ATTR_EMULATE_PREPARES => 1);

    据称emulate方式的性能比prepare要好,所以这也算安慰奖了。

    专门的lua脚本分支

    lua脚本开发滞后,是一个比较严重的问题,所以在邮件组上看到有个新的lua脚本分支出来 —— https://launchpad.net/mysql-proxy-lua-scripts。希望开发速度能跟上来。

    keepalive参数

    mysql proxy还不算太稳定,偶尔crash我也不觉得惊讶,所以新增的keepalive参数很有用。在proxy启动的时候,加上–keepalive参数,它便会努力保持proxy的运行状态,停止了也会自动重启。

    在centos 5.2下安装最新的mysql proxy

    mysql proxy的代码树已经迁移到lauchpad,采用bazaar进行版本管理。参考了一些文档,在centos 5.2下编译安装最新mysql proxy成功。步骤记录如下(在centos 5下应该也适用):

    首先让EPEL (Extra Packages for Enterprise Linux) repository 生效

     # rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-2.noarch.rpm 

    确定这些包已经安装:GNU Autotools, flex, pkg-config, bazaar, MySQL client libraries

     # yum install autoconf automake libtool flex pkgconfig bzr mysql-devel 

    centos下自带的libevent版本超老,这个没有别的办法,只能自己重新编译,版本需要在1.4.0以上,越高越好

    
    $ wget http://monkey.org/~provos/libevent-1.4.9-stable.tar.gz 
    $ tar zvfx libevent-1.4.9-stable.tar.gz 
    $ cd libevent-1.4.9-stable 
    $ ./configure 
    $ make 
    # make install
    

    centos自带的glib版本也比较老,mysql proxy 0.7.0以上需要glib2 2.16.0以上才能编译成功,因此不得不重新编译glib

    
    $ wget http://ftp.gnome.org/pub/gnome/sources/glib/2.18/glib-2.18.4.tar.gz 
    $ tar zvfx glib-2.18.4.tar.gz 
    $ cd glib-2.18.4 
    $ ./configure 
    $ make 
    # make install
    

    编译安装lua 5.1

    
    $ wget http://www.lua.org/ftp/lua-5.1.4.tar.gz 
    $ tar zvfx lua-5.1.4.tar.gz 
    $ cd lua-5.1.4 
    $ vi src/Makefile 
    

    在CFLAGS里加上-fPIC,因为我在64位机上编译出现了“relocations”错误

    
    $ make linux 
    # make install 
    # cp etc/lua.pc /usr/local/lib/pkgconfig/
    

    重要:让pkg-config找到自己编译的库在哪里

    
    $ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
    

    最后,从bazaar中检出最新的mysql-proxy源文件进行编译:

    
    $ bzr branch lp:mysql-proxy 
    $ cd mysql-proxy 
    $ ./autogen.sh 
    $ ./configure 
    $ make 
    # make install
    

    编译完成,可以检查一下最终结果:

    
    # mysql-proxy -V
    
    
    mysql-proxy 0.7.0
      glib2: 2.18.4
      libevent: 1.4.9-stable
      admin: 0.7.0
      proxy: 0.7.0
    

    配置启动脚本,在系统启动时运行mysql-proxy

    
    cd /etc/init.d
    wget http://customcode.googlecode.com/files/mysql-proxy
    chmod 0755 /etc/init.d/mysql-proxy 
    chkconfig mysql-proxy on 
    

    增加运行参数

    
    cat >/etc/sysconfig/mysql-proxy 
    
    
    # Options to mysql-proxy 
    # do not remove --daemon 
    PROXY_OPTIONS="--daemon" 
    

    CTRL+D保存,然后就可以使用以下命令启动|停止mysql-proxy

    
    /etc/init.d/mysql-proxy start|stop
    

    参考文档:
    How to Compile and Install MySQL Proxy from Bazaar on CentOS 5.2

    mysql proxy的常见问题

    最近试用mysql proxy,遇到若干问题,好在一一找到了解决方案,列出来备忘。这次使用的版本是0.6.x,也许新版本就没有这些问题了。

    无法通过mysql proxy连接mysql

    在host,password正确的情况下,也会遇到无法连接mysql的情况,可以查查mysql server是不是使用了old_password,检查my.cnf里面是不是有

    old_password = 1

    有的数据库是从老版本升级上来的,所以会开启这个选项,mysql proxy不支持old_password。另外也可以通过查看密码长度的方式来判断:

    select length(password) from mysql.user 

    如果长度为16位则是old_password无疑。

    字符乱码

    通过proxy连上数据库之后,查到的字符串始终是乱码,即便手工执行了set names ‘utf8’也没有效果。

    解决办法,mysql server必须设置

    
    [mysqld]
    skip-character-set-client-handshake
    init-connect='SET NAMES utf8'
    default-character-set=utf8

    一台mysql slave当掉之后,mysql proxy会报错导致全部的mysql无法连接

    安装了mysql proxy实现读写分离,有master x 1, slave x 2。为了测试failover,停掉了一个slave,然后mysql proxy会一直报错,提示无法连接。这个情况比单点的mysql还糟糕,挂掉一个就全挂掉!mysql的工程师给提供了一段代码,替换掉src/network-mysqld-proxy.cNETWORK_MYSQLD_PLUGIN_PROTO函数可以解决这个问题。

    代码比较长,直接附上下载地址: network-mysqld-proxy-function.c

    定期crash

    这个问题也很糟糕,mysql proxy经常会自己悄悄的停止工作,所幸时间间隔很长。猜想是有内存泄漏的问题存在,希望以后的版本能解决。

    我采用的解决办法就是晚上定期重启它。

    用mysql触发器自动更新memcache

    mysql 5.1支持触发器以及自定义函数接口(UDF)的特性,如果配合libmemcache以及Memcached Functions for MySQL,就能够实现memcache的自动更新。简单记录一下安装测试步骤。

    安装步骤

    • 安装memcached,这个步骤很简单,随处可见
    • 安装mysql server 5.1RC,安装办法也很大众,不废话了
    • 编译libmemcached,解压后安装即可
      ./configure; make; make install
    • 编译Memcached Functions for MySQL,在http://download.tangent.org/找一个最新的版本下载就是,
      ./configure --with-mysql=/usr/local/mysql/bin/mysql_config --libdir=/usr/local/mysql/lib/mysql/
      make
      make install
    • 接下来有两个办法让Memcached Functions for MySQL在mysql中生效
      • 在mysql的shell中执行memcached_functions_mysql源码目录下的sql/install_functions.sql,这会把memcache function作为UDF加入mysql
      • 运行memcached_functions_mysql源码目录下的utils/install.pl,这是一个perl脚本,作用同上一条

    测试memcache function

    以下测试脚本摘自memcached_functions_mysql的源码目录,有兴趣可以试试

    drop table if exists urls;
    create table urls (
      id int(3) not null,
      url varchar(64) not null default '',
      primary key (id)
      );
    
    select memc_servers_set('localhost:11211');
    select memc_set('urls:sequence', 0);
    
    DELIMITER |
    
    DROP TRIGGER IF EXISTS url_mem_insert;
    CREATE TRIGGER url_mem_insert
    BEFORE INSERT ON urls
    FOR EACH ROW BEGIN
        SET NEW.id= memc_increment('urls:sequence');
        SET @mm= memc_set(concat('urls:',NEW.id), NEW.url);
    END |
    
    DELIMITER ;
    
    insert into urls (url) values ('http://google.com');
    insert into urls (url) values ('http://www.ooso.net/index.php');
    insert into urls (url) values ('http://www.ooso.net/');
    insert into urls (url) values ('http://slashdot.org');
    insert into urls (url) values ('http://mysql.com');
    select * from urls;
    
    select memc_get('urls:1');
    select memc_get('urls:2');
    select memc_get('urls:3');
    select memc_get('urls:4');
    select memc_get('urls:5');

    混合使用SQL和shell命令

    mysql下可以用批处理模式运行SQL,比如:

    shell> mysql -h host -u user -p < batch-file.sql
    Enter password: ********

    但是平常往往需要在执行sql的同时,运行一些shell脚本进行进一步计算,保存日志之类的。这个可以靠mysql的system命令来实现,例如:

    # Mixing shell commands and SQL queries in batch mode- demo script
    use test;
    #INSTALL PLUGIN example SONAME 'ha_example.so'; #Ignore statement
    system cp /tmp/mysqld.trace logs/init.trace # Shell commands prefixed with a 'system'
    create table new4(num integer) engine=EXAMPLE;
    system cp /tmp/mysqld.trace logs/after_create.trace # Shell commands prefixed with a 'system'
    system diff logs/init.trace logs/after_create.trace # Shell commands prefixed with a 'system'

    注意

    • system命令只在类unix操作系统上有效。
    • 可以在存储过程中使用system。

    在命令行里执行mysql的sql

    平常执行sql,需要登录到mysql的shell下,然后再执行。比如:

    $ /usr/bin/mysql -u root
    mysql> select * from users;

    但是如果写一点简单的脚本,也可以在命令行下直接运行sql并显示结果,比如:

    
    $ cat executemysql.sh
    #!/bin/sh
    
    qry=$1;
    
    echo "Executing the following query"
    echo "$qry"
    
    mysql -u root << eof
    $qry
    eof

    运行一下:

    $ ./executemysql.sh "select id,name,age from test.users limit 2"
    Executing the following query
    select id,name,age from test.users limit 2
    id name age
    3 john 20
    4 tom 21

    原文:Execute mysql from bash script

    PDO_MYSQL的一些预定义常量

    PDO_MYSQL是PHP Data Objects (PDO) interface的一个mysql扩展。仔细看看php手册上面,其实还是有些有趣的参数可供使用,例如:

    PDO::MYSQL_ATTR_INIT_COMMAND (integer)

    Command to execute when connecting to the MySQL server. Will automatically be re-executed when reconnecting.

    当我使用PDO_MYSQL连上mysql以后,可以利用这个参数自动执行一些QUERY。最常见的使用场合是连接mysql使用utf-8字符集:

    $db = new PDO("mysql:dbname=dbname", "user", "password", 
    	array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"));

    以上代码会在连上mysql之后马上执行sql:

    set names 'utf8';