MySQL数据恢复和复制对InnoDB锁机制的影响

MySQL通过BINLOG记录执行成功的INSERT,UPDATE,DELETE等DML语句。并由此实现数据库的恢复(point-in-time)和复制(其原理与恢复类似,通过复制和执行二进制日志使一台远程的MySQLl数据库,多称为slave,进行实时同步)。MySQL 5.5.x以后的版本支持3种日志格式。通过binlog_format参数设置。该参数影响了记录二进制日志的格式,十分重要。

  1. STATEMENT格式和之前的MySQL版本一样,二进制日志文件记录的是日志的逻辑SQL语句。

  2. ROW格式记录的不再是简单的SQL语句,而是记录表的每行记录更改的情况。

  3. 在MIXED格式下,MySQL默认采用STATEMENT格式进行二进制日志文件的记录。但是在一些特殊情况下会使用ROW格式,可能的情况如下:

  • 表的存储引擎为NDB,这时对表的DML操作都会以ROW格式记录。

  • 使用了UUID(),USER(),CURRENT_USER(),FOUND_ROWS(),ROW_COUNT()等不确定函数。

  • 使用了INSERT DELAY语句。

  • 使用了用户自定义函数(UDF).

  • 使用了临时表(temporary table) 。

对于基于语句的日志格式(STATEMENT)的恢复和复制而言,由于MySQL的BINLONG是按照事务(transaction)提交(committed)的先后顺序记录的,因此要正确恢复或者复制数据,就必须满足:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读(Phantom Problem)。这已经超过了ISO/ANSI SQL92″可重复读(Repeatable Read)”隔离级别的要求,实际上是要求事务要串行化。这也是许多情况下,InnoDB要用到Next-Key Lock锁的原因,比如用在范围条件更新记录时,无论是在Read Committed或者是Repeatable Read隔离级别下,InnoDB都要使用Next-key Lock锁。既然说到Next-key Lock锁机制,我这里简单说一下,演示各种效果就让童鞋们自己去测试了^_^

InnoDB锁的算法

innodb引擎有三种锁的算法设计:

  • Record lock:对单个索引项加锁
  • Gap lock:间隙锁,对索引项之间的”间隙”,第一条记录前的”间隙”或最后一条记录后的”间隙”加锁,不包括索引项本身
    Next-key lock:Gap lock+Next-key lock 锁定索引项范围。对记录及其前面的间隙加锁

注意:

  • 对于唯一索引,其加上的是Record Lock,仅锁住记录本身。但也有特别情况,那就是唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的其中一个,那么加锁的情况依然是Next-key lock。

  • 对于辅助索引,其加上的是Next-Key Lock,锁定的是范围,包含记录本身。

  • 另外如果使用相等的条件给一个不存在的记录加锁,innodb也会使用Next-key lock

特别注意:

  • innodb存储引擎是通过给索引上的索引项加锁来实现,这意味着:只有通过索引条件检索数据,innodb才会使用行锁,否则,innodb将使用表锁。(Repeatable Read隔离级别下)

  • 如果是在表没有主键或者没有任何索引的情况下(并且是在read committed隔离级别)。如果一个表有主键,没有其他的索引,检索条件又不是主键,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。

对于”INSERT INTO target_tab SELECT * FROM source_tab WHERE….” 和”CREATE TABLE new_tab…SELECT….FROM source_tab WHERE…(CTAS)”这种SQL语句,用户并没有对source_tab做任何操作,但是MySQL会对这种SQL语句做特别的处理。我们来看一个实际的例子:

mysql> select * from source_tab;
+------+------+--------+
| id   | age  | name   |
+------+------+--------+
|    1 |   24 | yayun  |
|    2 |   24 | atlas  |
|    3 |   25 | david  |
|    4 |   24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec)

mysql> select * from target_tab;
Empty set (0.00 sec)

mysql> desc source_tab;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  |     | NULL    |       |
| age   | int(11)     | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> desc target_tab;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  |     | NULL    |       |
| age   | int(11)     | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> 

CTAS操作给原表加锁的例子

session1操作

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

mysql> select * from  source_tab;
+------+------+--------+
| id   | age  | name   |
+------+------+--------+
|    1 |   24 | yayun  |
|    2 |   24 | atlas  |
|    3 |   25 | david  |
|    4 |   24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec)

mysql> insert into target_tab select * from source_tab where name='yayun';         #该语句执行以后,session2中的update操作将会等待
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.04 sec)

mysql> 

session2操作

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

mysql> select * from source_tab;
+------+------+--------+
| id   | age  | name   |
+------+------+--------+
|    1 |   24 | yayun  |
|    2 |   24 | atlas  |
|    3 |   25 | david  |
|    4 |   24 | dengyy |
+------+------+--------+
4 rows in set (0.00 sec)

mysql> update source_tab set name='dengyayun' where name='yayun';  #一直等待,除非session1执行commit提交。
Query OK, 1 row affected (49.24 sec)                               #可以看见用了49秒,这就是在等待session1提交,当session1提交后,顺利更新
Rows matched: 1  Changed: 1  Warnings: 0

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

mysql> 

在上面示例中,只是简单的读source_tab表的数据,相当于执行一个普通的SELECT语句,用一致性读就可以了。Oracle正是这么做的,它通过MVCC技术实现的多版本并发控制实现一致性读,不需要给source_tab加任何锁。大家都知道InnoDB也实现了多版本并发控制(MVCC),对普通的SELECT一致性读,也不需要加任何锁;但是这里InnoDB却给source_tab表加了共享锁,并没有使用多版本一致性读技术。

MySQL为什么这么做呢?why?其原因还是为了保证恢复和复制的正确性。因为在不加锁的情况下,如果上述语句执行过程中,其他事务对原表(source_tab)做了更新操作,就可能导致数据恢复结果错误。为了演示错误的发生,再重复上面的例子,先将系统变量innodb_locks_unsafe_for_binlog的值设为”on”,默认值是off。

innodb_locks_unsafe_for_binlog

设定InnoDB是否在搜索和索引扫描中使用间隙锁(gap locking)。InnoDB使用行级锁(row-level locking),通常情况下,InnoDB在搜索或扫描索引的行锁机制中使用“下一键锁定(next-key locking)”算法来锁定某索引记录及其前部的间隙(gap),以阻塞其它用户紧跟在该索引记录之前插入其它索引记录。站在这个角度来说,行级锁也叫索引记录锁(index-record lock)。

默认情况下,此变量的值为OFF,意为禁止使用非安全锁,也即启用间隙锁功能。将其设定为ON表示禁止锁定索引记录前的间隙,也即禁用间隙锁,InnoDB仅使用索引记录锁(index-record lock)进行索引搜索或扫描,不过,这并不禁止InnoDB在执行外键约束检查或重复键检查时使用间隙锁。

启用innodb_locks_unsafe_for_binlog的效果类似于将MySQL的事务隔离级别设定为READ-COMMITTED,但二者并不完全等同:
innodb_locks_unsafe_for_binlog是全局级别的设定且只能在服务启动时设定,而事务隔离级别可全局设定并由会话级别继承,然而会话级别也以按需在运行时对其进行调整。类似READ-COMMITTED事务隔离级别,启用innodb_locks_unsafe_for_binlog也会带来“幻影问题(phantom problem)”,但除此之外,它还能带来如下特性:

  • 对UPDATE或DELETE语句来说,InnoDB仅锁定需要更新或删除的行,对不能够被WHERE条件匹配的行施加的锁会在条件检查后予以释放。这可以有效地降低死锁出现的概率;

  • 执行UPDATE语句时,如果某行已经被其它语句锁定,InnoDB会启动一个“半一致性(semi-consistent)”读操作从MySQL最近一次提交版本中获得此行,并以之判定其是否能够并当前UPDATE的WHERE条件所匹配。如果能够匹配,MySQL会再次对其进行锁定,而如果仍有其它锁存在,则需要先等待它们退出。

其无法动态修改,需要修改配置文件,演示如下:

CTAS操作不给原表加锁带来的安全问题

mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | MIXED |
+---------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'innodb_locks_unsafe%';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_locks_unsafe_for_binlog | ON    |
+--------------------------------+-------+
1 row in set (0.00 sec)

mysql> 

session1操作

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

mysql> select * from source_tab where id=1;
+------+------+-----------+
| id   | age  | name      |
+------+------+-----------+
|    1 |   24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec)

mysql> insert into target_tab select * from source_tab where id=1;        
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> commit;    #插入操作后提交
Query OK, 0 rows affected (0.01 sec)

mysql> select * from source_tab where name='good yayun'; #此时查看数据,target_tab中可以插入source_tab更新前的结果,这复合应用逻辑
+------+------+------------+
| id   | age  | name       |
+------+------+------------+
|    1 |   24 | good yayun |
+------+------+------------+
1 row in set (0.00 sec)

mysql> select * from target_tab;
+------+------+-----------+
| id   | age  | name      |
+------+------+-----------+
|    1 |   24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec)

session2操作

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

mysql> select * from source_tab where id=1;
+------+------+-----------+
| id   | age  | name      |
+------+------+-----------+
|    1 |   24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec)

mysql> update source_tab set name='good yayun' where id=1;  # session1未提交,可以对session1中的select记录进行更新操作
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;       # 更新操作先提交
Query OK, 0 rows affected (0.02 sec)

mysql> select * from source_tab where name='good yayun';
+------+------+------------+
| id   | age  | name       |
+------+------+------------+
|    1 |   24 | good yayun |
+------+------+------------+
1 row in set (0.00 sec)

mysql> select * from target_tab;
+------+------+-----------+
| id   | age  | name      |
+------+------+-----------+
|    1 |   24 | dengyayun |
+------+------+-----------+
1 row in set (0.00 sec)

mysql> 

从上面的测试结果可以发现,设置系统变量innodb_locks_unsafe_for_binlog的值为”ON”后,innodb不再对原表(source_tab)加锁,结果也符合应用的逻辑,但是如果我们分析一下BINLOG内容,就可以发现问题所在

[root@MySQL-01 mysql]# mysqlbinlog mysql-bin.000120 | grep -A 20 'update source_tab'
update source_tab set name='good yayun' where id=1
/*!*/;
# at 468
#140401  2:04:12 server id 1  end_log_pos 495   Xid = 74
COMMIT/*!*/;
# at 495
#140401  2:04:23 server id 1  end_log_pos 563   Query   thread_id=5     exec_time=0     error_code=0
SET TIMESTAMP=1396289063/*!*/;
BEGIN
/*!*/;
# at 563
#140401  2:02:42 server id 1  end_log_pos 684   Query   thread_id=5     exec_time=0     error_code=0
SET TIMESTAMP=1396288962/*!*/;
insert into target_tab select * from source_tab where id=1
/*!*/;
# at 684
#140401  2:04:23 server id 1  end_log_pos 711   Xid = 73
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
[root@MySQL-01 mysql]# 

可以清楚的看到在BINLOG的记录中,更新操作的位置在INSERT……SELECT之前,如果使用这个BINLOG进行数据库恢复,恢复的结果则与实际的应用逻辑不符;如果进行复制,就会导致主从数据不一致!

通过上面的例子,相信童鞋们不难理解为什么MySQL在处理

“INSERT INTO target_tab SELECT * FROM source_tab WHERE….”

“CREATE TABLE new_tab….SELECT…..FROM source_tab WHERE….”

时要给原表(source_tab)加锁,而不是使用对并发影响最小的多版本数据来实现一致性读。还要特别说明的是,如果上述语句的SELECT是范围条件,innodb还会给原表加上Next-Key Lock锁。

因此,INSERT….SELECT和CREATE TABLE….SELECT…..语句,可能会阻止对原表的并发更新。如果查询比较复杂,会照成严重的性能问题,生产环境需要谨慎使用。

总结如下:

如果应用中一定要用这种SQL来实现业务逻辑,又不希望对源表的并发更新产生影响,可以使用下面3种方法:

  1. 将innodb_locks_unsafe_for_binlog的值设置为”ON”,强制MySQL使用多版本数据一致性读。但付出的代价是可能无法使用BINLOG正确的进行数据恢复或者主从复制。因此,此方法是不推荐使用的。

  2. 通过使用SELECT * FROM source_tab ….. INTO OUTFILE 和LOAD DATA INFILE…..语句组合来间接实现。采用这种放松MySQL不会给(源表)source_tab加锁。

  3. 使用基于行(ROW)的BINLOG格式和基于行的数据的复制。此方法是推荐使用的方法。

Nginx+uWSGI+Mysql+Django+Virtualenv环境部署

安装软件包

我的Linux是Ubuntu,需要安装Python3,如果你需要安装其他版本替换即可

sudo apt-get install mysql-server python3 python-dev python3-pip virtualenv

配置Virtualenv及Python环境

首先,这个Virtualenv并不是必须的,他的作用就是创建一个“隔离”的Python运行环境。

我想大家都碰到过在一台电脑上同时开发多个应用程序,如果应用A需要Python 2.7,而应用B需要Python 2.6怎么办?有时还会碰到不只是Python版本的不一致,比如Django等软件包版本。

这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。

1、新建独立运行环境,命名为pyblog_env

virtualenv pyblog_env --python=python3.5 --no-site-packages

–python指定Python版本 –no-site-packages不复制系统已安装Python包

2、进入pyblog_env环境

source pyblog_env/bin/activate

这时会发现命令提示符前面多了一个(pyblog_env),变成(pyblog_env)user@hostname:~$ 这种形式,这表示我们已经进入到刚刚创建的虚拟环境。 然后再通过pip安装包,都会安装到这个环境下,不会对系统环境造成影响。 退出虚拟环境命令:deactivate

3、安装项目依赖

pip3 install django PyMySQL mysqlclient Pillow
#如果有需求文件,比如requirements.txt,则执行
pip3 install -r requirements.txt

Django项目配置

1、上传Django项目,我的项目在GitHub。

git clone https://github.com/zytx/pyblog.git

2、配置项目的数据库信息

3、数据迁移

python manage.py makemigrations
python manage.py migrate

4、建立超级用户:

python manage.py createsuperuser

5、收集静态文件:

python manage.py collectstatic

6、测试项目 运行开发服务器看项目能否正常运行

python manage.py runserver 0.0.0.0:8000

访问ip:8000看是否出错

uWSGI 配置

退出刚才进入的虚拟环境,使用命令:deactivate

1、安装uWSGI

pip3 install uwsgi

2、运行测试:

uwsgi --http :8000 --home /home/zytx/pyblog_env/ --chdir /home/zytx/pyblog_env/pyblog -w pyblog.wsgi

–home Virtualenv的虚拟环境目录 –chdir Django项目目录

如果访问IP:8000可以看到项目,就说明Python环境和uWSGI是没有问题的

3、创建配置文件 接下来在项目里新建一个配置文件uwsgi.ini

[uwsgi]
# 项目根目录路径(full path)
chdir           = /home/zytx/pyblog_env/pyblog/
# Django的 wsgi 文件
module          = pyblog.wsgi:application
# virtualenv目录 (full path)
home            = /home/zytx/pyblog_env/

master          = true
# 最大工作进程数(CPU密集型建议设为CPU核心数,IO密集型建议设为CPU核心数的两倍)
processes       = 2
# unix套接字文
socket          = /home/zytx/pyblog_env/pyblog.sock
# socket文件权限
chmod-socket    = 777
# 退出时清空环境
vacuum          = true

4、后台启动uWSGI

uwsgi /home/zytx/pyblog_env/pyblog/uwsgi.ini&

Nginx 配置

1、创建配置文件

sudo vi /etc/nginx/sites-available/pyblog

加入如下内容:

server {
    listen 80;
    server_name www.immrz.com;
    root /home/zytx/pyblog_env/pyblog/;

        location /media/  {
            #如果你的media目录不在root里,可以配置 alias /path/to/your/mysite/media;
        }

        location  /static/ {
            #如果你的static目录不在root里,可以配置 alias /path/to/your/mysite/static;
        }

        location / {
            uwsgi_pass  unix:///home/zytx/pyblog_env/pyblog.sock;
            include uwsgi_params; # the uwsgi_params file you installed
        }
}

软连接到sites-enabled

sudo ln -s /etc/nginx/sites-available/pyblog /etc/nginx/sites
enabled/

2、重新加载Nginx配置

sudo service nginx reload

如果配置无误我们就可以让uWSGI开机运行

#编辑/etc/rc.local,在exit0之前加入
/usr/local/bin/uwsgi --ini /home/zytx/pyblog_env/pyblog/pyblog.uwsgi.ini&

使用docker构建部署django+mysql项目(docker-compose)

这里需要升级docker版本,因为centos7 yum源默认自带的docker版本无法使用compose,详情见: http://nanguawu.me/container/5013.html

容器部署目录结构:

[root@vm2 web_django]# tree -L 2
.
├── db
│   ├── auto.cnf
│   ├── ca-key.pem
│   ├── ca.pem
│   ├── client-cert.pem
│   ├── client-key.pem
│   ├── data01
│   ├── ib_buffer_pool
│   ├── ibdata1
│   ├── ib_logfile0
│   ├── ib_logfile1
│   ├── mysql
│   ├── performance_schema
│   ├── private_key.pem
│   ├── public_key.pem
│   ├── server-cert.pem
│   ├── server-key.pem
│   └── sys
├── docker-compose.yml
├── Dockerfile
├── manage.py
├── requirements.txt
└── website
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py
6 directories, 21 files

requirements.txt文件内容:

django==1.10.8
MySQL-python

Dockfile文件内容:

FROM python:2.7
MAINTAINER Larryliang "[email protected]"
ENV PYTHONUNBUFFERD 1
RUN mkdir /code
RUN mkdir /code/db
RUN mkdir /code/website
WORKDIR /code
ADD requirements.txt /code/
RUN  pip install -r requirements.txt -i https://pypi.douban.com/simple   --trusted-host pypi.douban.com
ADD . /code/

docker-compose.yml文件内容:

version: '3'
services:
  db:
    image: mysql
    expose: 
      - "3306"
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=data01
      - MYSQL_ROOT_PASSWORD=data01
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

Build项目

[root@vm2 web_django]# docker version
Client:
 Version:      17.07.0-ce
 API version:  1.31
 Go version:   go1.8.3
 Git commit:   8784753
 Built:        Tue Aug 29 17:42:01 2017
 OS/Arch:      linux/amd64
Server:
 Version:      17.07.0-ce
 API version:  1.31 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   8784753
 Built:        Tue Aug 29 17:43:23 2017
 OS/Arch:      linux/amd64
 Experimental: false
[root@vm2 web_django]# docker-compose  build
db uses an image, skipping
Building web
Step 1/10 : FROM python:2.7
 ---> 8a90a66b719a
Step 2/10 : MAINTAINER Larryliang "[email protected]"
 ---> Using cache
 ---> 580a124b98a8
Step 3/10 : ENV PYTHONUNBUFFERD 1
 ---> Using cache
 ---> 87f8ce3ca0a2
Step 4/10 : RUN mkdir /code
 ---> Using cache
 ---> 83f26201a74f
Step 5/10 : RUN mkdir /code/db
 ---> Using cache
 ---> 06c6e22dd86a
Step 6/10 : RUN mkdir /code/website
 ---> Using cache
 ---> c0abb5fd2218
Step 7/10 : WORKDIR /code
 ---> Using cache
 ---> 942e3b5a8e20
Step 8/10 : ADD requirements.txt /code/
 ---> Using cache
 ---> 0a4a0bd7b379
Step 9/10 : RUN pip install -r requirements.txt -i https://pypi.douban.com/simple   --trusted-host pypi.douban.com
 ---> Using cache
 ---> 09600fc029fd
Step 10/10 : ADD . /code/
 ---> 11773f943a61
Successfully built 11773f943a61
Successfully tagged webdjango_web:latest

创建项目和APP

[root@vm2 web_django]# docker-compose  run web django-admin.py startproject website  .
Creating network "webdjango_default" with the default driver
Pulling db (mysql:latest)...
latest: Pulling from library/mysql
ad74af05f5a2: Already exists
0639788facc8: Pull complete
de70fa77eb2b: Pull complete
724179e94999: Pull complete
50c77fb16ba6: Pull complete
d51f459239fb: Pull complete
937bbdd4305a: Pull complete
35369f9634e1: Pull complete
f6016aab25f1: Pull complete
5f1901e920da: Pull complete
fdf808213c5b: Pull complete
Digest: sha256:96edf37370df96d2a4ee1715cc5c7820a0ec6286551a927981ed50f0273d9b43
Status: Downloaded newer image for mysql:latest
Creating webdjango_db_1 ... 
Creating webdjango_db_1 ... done

修改配置website/settings.py如下,之后重新build

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'data01',
        'USER': 'root',
        'PASSWORD': 'data01',
        'HOST': 'db',
        'PORT': '3306',
    }
}

启动容器:

[root@vm2 web_django]# docker-compose  up
Starting webdjango_db_1 ... 
Starting webdjango_db_1 ... done
Recreating webdjango_web_1 ... 
Recreating webdjango_web_1 ... done
Attaching to webdjango_db_1, webdjango_web_1
db_1   | 2017-09-07T12:22:23.513202Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1   | 2017-09-07T12:22:23.515941Z 0 [Note] mysqld (mysqld 5.7.19) starting as process 1 ...
db_1   | 2017-09-07T12:22:23.612191Z 0 [Note] InnoDB: PUNCH HOLE support available
db_1   | 2017-09-07T12:22:23.612240Z 0 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
db_1   | 2017-09-07T12:22:23.612247Z 0 [Note] InnoDB: Uses event mutexes
db_1   | 2017-09-07T12:22:23.612252Z 0 [Note] InnoDB: GCC builtin __atomic_thread_fence() is used for memory barrier
db_1   | 2017-09-07T12:22:23.612256Z 0 [Note] InnoDB: Compressed tables use zlib 1.2.3
db_1   | 2017-09-07T12:22:23.612260Z 0 [Note] InnoDB: Using Linux native AIO
db_1   | 2017-09-07T12:22:23.612510Z 0 [Note] InnoDB: Number of pools: 1
db_1   | 2017-09-07T12:22:23.612640Z 0 [Note] InnoDB: Using CPU crc32 instructions
db_1   | 2017-09-07T12:22:23.699132Z 0 [Note] InnoDB: Initializing buffer pool, total size = 128M, instances = 1, chunk size = 128M
db_1   | 2017-09-07T12:22:23.706780Z 0 [Note] InnoDB: Completed initialization of buffer pool
db_1   | 2017-09-07T12:22:23.708851Z 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread priority can be changed. See the man page of setpriority().
db_1   | 2017-09-07T12:22:23.721021Z 0 [Note] InnoDB: Highest supported file format is Barracuda.
db_1   | 2017-09-07T12:22:24.210121Z 0 [Note] InnoDB: Creating shared tablespace for temporary tables
db_1   | 2017-09-07T12:22:24.210959Z 0 [Note] InnoDB: Setting file './ibtmp1' size to 12 MB. Physically writing the file full; Please wait ...
db_1   | 2017-09-07T12:22:24.884250Z 0 [Note] InnoDB: File './ibtmp1' size is now 12 MB.
db_1   | 2017-09-07T12:22:24.937435Z 0 [Note] InnoDB: 96 redo rollback segment(s) found. 96 redo rollback segment(s) are active.
db_1   | 2017-09-07T12:22:24.937508Z 0 [Note] InnoDB: 32 non-redo rollback segment(s) are active.
db_1   | 2017-09-07T12:22:24.938536Z 0 [Note] InnoDB: Waiting for purge to start
db_1   | 2017-09-07T12:22:24.989070Z 0 [Note] InnoDB: 5.7.19 started; log sequence number 12143700
db_1   | 2017-09-07T12:22:24.989543Z 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
db_1   | 2017-09-07T12:22:24.992481Z 0 [Note] Plugin 'FEDERATED' is disabled.
db_1   | 2017-09-07T12:22:25.148161Z 0 [Note] Found ca.pem, server-cert.pem and server-key.pem in data directory. Trying to enable SSL support using them.
db_1   | 2017-09-07T12:22:25.186490Z 0 [Warning] CA certificate ca.pem is self signed.
db_1   | 2017-09-07T12:22:25.189451Z 0 [Note] Server hostname (bind-address): '*'; port: 3306
db_1   | 2017-09-07T12:22:25.189543Z 0 [Note] IPv6 is available.
db_1   | 2017-09-07T12:22:25.189564Z 0 [Note]   - '::' resolves to '::';
db_1   | 2017-09-07T12:22:25.189595Z 0 [Note] Server socket created on IP: '::'.
db_1   | 2017-09-07T12:22:25.314065Z 0 [Note] InnoDB: Buffer pool(s) load completed at 170907 12:22:25
db_1   | 2017-09-07T12:22:25.320253Z 0 [Warning] 'user' entry 'root@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.320383Z 0 [Warning] 'user' entry 'mysql.sys@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.320534Z 0 [Warning] 'db' entry 'performance_schema mysql.session@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.320580Z 0 [Warning] 'db' entry 'sys mysql.sys@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.337153Z 0 [Warning] 'proxies_priv' entry '@ root@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.486920Z 0 [Warning] 'tables_priv' entry 'user mysql.session@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.486995Z 0 [Warning] 'tables_priv' entry 'sys_config mysql.sys@localhost' ignored in --skip-name-resolve mode.
db_1   | 2017-09-07T12:22:25.741875Z 0 [Note] Event Scheduler: Loaded 0 events
db_1   | 2017-09-07T12:22:25.742708Z 0 [Note] mysqld: ready for connections.
db_1   | Version: '5.7.19'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
db_1   | 2017-09-07T12:22:25.742789Z 0 [Note] Executing 'SELECT * FROM INFORMATION_SCHEMA.TABLES;' to get a list of tables using the deprecated partition engine. You may use the startup option '--disable-partition-engine-check' to skip this check. 
db_1   | 2017-09-07T12:22:25.742799Z 0 [Note] Beginning of list of non-natively partitioned tables
db_1   | 2017-09-07T12:22:25.877821Z 0 [Note] End of list of non-natively partitioned tables

或者放到后台启动:

[root@vm2 web_django]# docker-compose  up -d
Starting webdjango_db_1 ... 
Starting webdjango_db_1 ... done
Starting webdjango_web_1 ... 
Starting webdjango_web_1 ... done

测试结果:

[root@vm2 web_django]# curl  http://192.168.100.120:8000/ 
<!DOCTYPE html>
<html lang="en"><head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="robots" content="NONE,NOARCHIVE"><title>Welcome to Django</title>
  <style type="text/css">
    html * { padding:0; margin:0; }
    body * { padding:10px 20px; }
    body * * { padding:0; }
    body { font:small sans-serif; }
    body>div { border-bottom:1px solid #ddd; }
    h1 { font-weight:normal; }
    h2 { margin-bottom:.8em; }
    h2 span { font-size:80%; color:#666; font-weight:normal; }
    h3 { margin:1em 0 .5em 0; }
    h4 { margin:0 0 .5em 0; font-weight: normal; }
    table { border:1px solid #ccc; border-collapse: collapse; width:100%; background:white; }
    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
    thead th {
      padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
      font-weight:normal; font-size:11px; border:1px solid #ddd;
    }
    tbody th { width:12em; text-align:right; color:#666; padding-right:.5em; }
    #summary { background: #e0ebff; }
    #summary h2 { font-weight: normal; color: #666; }
    #explanation { background:#eee; }
    #instructions { background:#f6f6f6; }
    #summary table { border:none; background:transparent; }
  </style>
</head>
<body>
<div id="summary">
  <h1>It worked!</h1>
  <h2>Congratulations on your first Django-powered page.</h2>
</div>
<div id="instructions">
  <p>
    Of course, you haven't actually done any work yet. Next, start your first app by running <code>python manage.py startapp [app_label]</code>.
  </p>
</div>
<div id="explanation">
  <p>
    You're seeing this message because you have <code>DEBUG = True</code> in your Django settings file and you haven't configured any URLs. Get to work!
  </p>
</div>
</body></html>

观察容器状态:

[root@vm2 web_django]# docker ps  
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
2d549e242606        webdjango_web       "python manage.py ..."   7 minutes ago       Up 50 seconds       0.0.0.0:8000->8000/tcp   webdjango_web_1
ce18133de30c        mysql               "docker-entrypoint..."   About an hour ago   Up 51 seconds       3306/tcp                 webdjango_db_1
[root@vm2 web_django]# docker top 2d549e242606  #查看容器中进程
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                20355               20335               0                   08:28               ?                   00:00:00            python manage.py runserver 0.0.0.0:8000
root                20433               20355               1                   08:28               ?                   00:00:05            /usr/local/bin/python manage.py runserver 0.0.0.0:8000
[root@vm2 web_django]# docker exec -it  2d549e242606 df -Th  #容器中执行一条命令并返回
Filesystem          Type     Size  Used Avail Use% Mounted on
overlay             overlay   27G  3.9G   24G  15% /
tmpfs               tmpfs    489M     0  489M   0% /dev
tmpfs               tmpfs    489M     0  489M   0% /sys/fs/cgroup
/dev/mapper/cl-root xfs       27G  3.9G   24G  15% /code
shm                 tmpfs     64M     0   64M   0% /dev/shm
tmpfs               tmpfs    489M     0  489M   0% /sys/firmware
[root@vm2 web_django]# docker diff  2d549e242606  #查看容器变化
C /usr
C /usr/local
C /usr/local/lib
C /usr/local/lib/python2.7
A /usr/local/lib/python2.7/decimal.pyc
A /usr/local/lib/python2.7/UserList.pyc
A /usr/local/lib/python2.7/argparse.pyc
A /usr/local/lib/python2.7/BaseHTTPServer.pyc
C /usr/local/lib/python2.7/wsgiref
A /usr/local/lib/python2.7/wsgiref/__init__.pyc
A /usr/local/lib/python2.7/wsgiref/simple_server.pyc
A /usr/local/lib/python2.7/wsgiref/util.pyc
A /usr/local/lib/python2.7/wsgiref/handlers.pyc
A /usr/local/lib/python2.7/wsgiref/headers.pyc
A /usr/local/lib/python2.7/imghdr.pyc
C /usr/local/lib/python2.7/email
C /usr/local/lib/python2.7/email/mime
A /usr/local/lib/python2.7/email/mime/nonmultipart.pyc
A /usr/local/lib/python2.7/email/mime/message.pyc
A /usr/local/lib/python2.7/email/mime/base.pyc
A /usr/local/lib/python2.7/email/mime/multipart.pyc
A /usr/local/lib/python2.7/email/mime/audio.pyc
A /usr/local/lib/python2.7/email/mime/image.pyc
A /usr/local/lib/python2.7/email/mime/text.pyc
A /usr/local/lib/python2.7/sndhdr.pyc
[root@vm2 web_django]# docker inspect 2d549e242606 
[
    {
        "Id": "2d549e2426067461adc1d635bb5f41db464bf0b8d57fbe57ddd9b5381c67e24e",
        "Created": "2017-09-07T12:22:22.395256812Z",
        "Path": "python",
        "Args": [
            "manage.py",
            "runserver",
            "0.0.0.0:8000"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 20355,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2017-09-07T12:28:57.89394279Z",
            "FinishedAt": "2017-09-07T12:28:52.523766859Z"
        },
        ...
        ...
            "Networks": {
                "webdjango_default": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "web",
                        "2d549e242606"
                    ],
                    "NetworkID": "fc66e543785ee4abb611ad6b3319717fb0f6af56fd710358990e0f0309d34b59",
                    "EndpointID": "54ef82cd8b8bcea0abe60e4c9428ab6401839ac1df6972be6da9b75923372994",
                    "Gateway": "172.18.0.1",
                    "IPAddress": "172.18.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:12:00:03",
                    "DriverOpts": null
                }
            }
        }
    }
]

关闭容器:

[root@vm2 web_django]# docker-compose  down 
Stopping webdjango_web_1 ... done
Stopping webdjango_db_1  ... done
Removing webdjango_web_1     ... done
Removing webdjango_web_run_5 ... done
Removing webdjango_web_run_4 ... done
Removing webdjango_web_run_3 ... done
Removing webdjango_web_run_2 ... done
Removing webdjango_web_run_1 ... done
Removing webdjango_db_1      ... done
Removing network webdjango_default

至此django环境搭建测试完毕。

xtrabackup备份还原MySQL数据库

mysqldump 备份鉴于其自身的某些特性(锁表,本质上备份出来insert脚本或者文本,不支持差异备份),不太适合对实时性要求比较高的情况

Xtrabackup可以解决mysqldump存在的上述的一些问题,生产环境应用的也会更多一些。

本文简单测试一下Xtrabackup对MySQL数据库的备份还原操作。

本着先把功能先撸起来再深入细节的原则,粗略地实现了一个备份还原,并未深入细节。

网上有不少xtrabackup的文章,因为环境不一样,有些需要配置xtrabackup的配置文件,
但是我在xtrabackup 2.4.7版本下测试就需要需求任何配置文件。可能是每个版本都的细节上都不一样,因此参考资料的时候要注意版本和环境。

innobackupex 备份

xtrabackup和MySQL的版本如下

未分类

完整备份

innobackupex --defaults-file=/etc/my.cnf --user=root --password=root --socket=/var/lib/mysql/mysql.sock /data/backup

说明:

1、 –defaults-file=/etc/my.cnf文件必须在最前面
2、 –user=root –password=root,–use=与 –password= 中间一定要有空格,

如截图,完整备份完成

未分类

如截图,完整备份会创建一个日期(年月日时分秒,yyyy-MM-dd_hh-mm-ss)命名的文件

完整备份出来的内存,实际上是对所备份的数据库的数据文件的copy加上备份时候产生的一些信息,
比如xtrabackup_checkpoints就是当前完整备份的一些个信息,这个信息对差异备份非常重要。

未分类

差异备份

差异备份之所以能够做到差异,就是依赖于完整备份的,在完整备份的基础上进行完整备份之后的差异的备份。
  
而如何确定完整的备份之后备份到哪里,就依赖于完整备份之后的xtrabackup_checkpoints这个文件的。
 

innobackupex --defaults-file=/etc/my.cnf --user=root --password=root --socket=/var/lib/mysql/mysql.sock --incremental /data/backup --incremental-basedir=/data/backup/2017-06-22_13-40-29

如截图,差异备份完成

未分类

如果在进行差异备份的时候,指定的完整备份的文件错误或者是未指定完整备份文件,会发现xtrabackup提示找不到xtrabackup_checkpoints这个文件。

未分类

innobackupex 还原

准备阶段

1、 恢复完整备份,也即完整备份应用(–apply-log)日志
    

innobackupex --defaults-file=/etc/my.cnf --apply-log --redo-only --socket=/var/lib/mysql/mysql.sock /data/backup/2017-06-22_13-40-29

2、 分别将增量备份应用到完整备份
  

innobackupex --defaults-file=/etc/my.cnf --apply-log --redo-only --socket=/var/lib/mysql/mysql.sock --incremental /data/backup/2017-06-22_13-40-29 --incremental-basedir=/data/backup/2017-06-22_13-41-48

未分类 

如果有多个差异备份,分别应用差异备份到完整备份。

恢复阶段

1、 完成差异备份的全部应用到完整备份之后,将恢复后的差异备份copy到原数据目录
    
默认情况下,如果数据路径下存在文件,则copy失败,需要清空数据文件路径下的文件。
    

innobackupex --copy-back /data/backup/2017-06-22_13-40-29

如截图,完成copy-back

未分类

2、启动MySQL服务

启动mysql服务,发现启动失败

未分类

看一下错误日志(启动错误信息),mysql5.7yum安装默认的errorlog位于/var/log/mysqld.log中,且默认不会滚动,意味着所有的错误信息都记录在这个文件中。

未分类

数据文件还原之后,需要读数据文件路径授予可读写的权限
  
这里直接授权数据文件路径777,chmod -R 777 /var/lib/mysql
  
然后启动mysql服务,可以正常启动。

未分类  

xtrabackupex才刚刚开始,留下一大堆问题,有时间再一个一个验证。

1、 怎么实现单个库(表)的备份与还原,毕竟实际环境中,因为每个库备份的频率和方式(备份方案)是不一样的?

2、 怎么用全备+差异备份然后结合二进制日志做基于时间点的方式还原?

3、 如何验证备份文件的有效性?

xtrabackup增量、全量备份mysql innodb教程

xtrabackup是Percona公司CTO Vadim参与开发的一款基于InnoDB的在线热备工具,具有开源,免费,支持在线热备,备份恢复速度快,占用磁盘空间小等特点,并且支持不同情况下的多种备份形式。xtrabackup的官方下载地址为http://www.percona.com/software/percona-xtrabackup。

xtrabackup包含两个主要的工具,即xtrabackup和innobackupex,二者区别如下:

  • xtrabackup只能备份innodb和xtradb两种引擎的表,而不能备份myisam引擎的表;

  • innobackupex是一个封装了xtrabackup的Perl脚本,支持同时备份innodb和myisam,但在对myisam备份时需要加一个全局的读锁。还有就是myisam不支持增量备份。

一、备份过程

innobackupex备份过程如下图:

未分类
(图1 innobackupex备份过程,本文中所有图都是google所得)

在图1中,备份开始时首先会开启一个后台检测进程,实时检测mysql redo的变化,一旦发现redo中有新的日志写入,立刻将日志记入后台日志文件xtrabackup_log中。之后复制innodb的数据文件和系统表空间文件ibdata1,待复制结束后,执行flush tables with read lock操作,复制.frm,MYI,MYD,等文件(执行flush tableswith read lock的目的是为了防止数据表发生DDL操作,并且在这一时刻获得binlog的位置)最后会发出unlock tables,把表设置为可读可写状态,最终停止xtrabackup_log。

二、全备恢复

这一阶段会启动xtrabackup内嵌的innodb实例,回放xtrabackup日志xtrabackup_log,将提交的事务信息变更应用到innodb数据/表空间,同时回滚未提交的事务(这一过程类似innodb的实例恢复)。恢复过程如下图:

未分类
(图2 innobackupex 恢复过程)

三、增量备份

innobackupex增量备份过程中的”增量”处理,其实主要是相对innodb而言,对myisam和其他存储引擎而言,它仍然是全拷贝(全备份)

“增量”备份的过程主要是通过拷贝innodb中有变更的”页”(这些变更的数据页指的是”页”的LSN大于xtrabackup_checkpoints中给定的LSN)。增量备份是基于全备的,第一次增备的数据必须要基于上一次的全备,之后的每次增备都是基于上一次的增备,最终达到一致性的增备。增量备份的过程如下,和全备的过程很类似,区别仅在第2步。

未分类
( 图 3 innobackupex增量备份过程)

四、增量备份恢复

和全备恢复类似,也需要两步,一是数据文件的恢复,如图4,这里的数据来源由3部分组成:全备份,增量备份和xtrabackup log。二是对未提交事务的回滚,如图5所示:

未分类
( 图4 innobackupex 增量备份恢复过程1)

未分类
( 图5 innobackupex增量备份恢复过程2)

五、innobackupex使用示例

1、安装使用xtrabackup

安装比较简单,我们使用二进制编译好的就行了,这种工具无需源码编译,因为没有什么功能需要俺们定制。

[root@MySQL-01 ~]# wget http://www.percona.com/redir/downloads/XtraBackup/LATEST/binary/Linux/x86_64/percona-xtrabackup-2.1.8-733-Linux-x86_64.tar.gz
[root@MySQL-01 ~]# tar xf percona-xtrabackup-2.1.8-733-Linux-x86_64.tar.gz -C /usr/local/
[root@MySQL-01 ~]# mv /usr/local/percona-xtrabackup-2.1.8-Linux-x86_64/ /usr/local/xtrabackup
[root@MySQL-01 ~]# echo "export PATH=$PATH:/usr/local/xtrabackup/bin" >> /etc/profile
[root@MySQL-01 ~]# source /etc/profile
[root@MySQL-01 ~]#

2、全量备份

创建备份用户:

mysql> create user 'backup'@'%' identified by 'yayun';
Query OK, 0 rows affected (0.01 sec)

mysql> grant reload,lock tables,replication client,create tablespace,super on *.* to 'backup'@'%';
Query OK, 0 rows affected (0.00 sec)

mysql>

进行全备份

备份数据存放在/data/backup/下面,innobackupex会自动创建一个文件夹,是当前系统的时间戳

mysql> select * from yayun.t1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | yayun |
|    2 | atlas |
+------+-------+
2 rows in set (0.00 sec)

mysql>

测试数据就是yayun库中的t1表 (错误:–host=192.168.199.1

[root@MySQL-01 ~]# innobackupex --user=backup --password=yayun --socket=/tmp/mysqld.sock --defaults-file=/etc/my.cnf /data/backup/
xtrabackup: Creating suspend file '/data/backup/2014-04-07_23-05-04/xtrabackup_log_copied' with pid '57608'
xtrabackup: Transaction log of lsn (5324782783) to (5324782783) was copied.
140407 23:06:14  innobackupex: All tables unlocked
innobackupex: Backup created in directory '/data/backup/2014-04-07_23-05-04'
innobackupex: MySQL binlog position: filename 'mysql-bin.000014', position 2983
140407 23:06:14  innobackupex: Connection to database server closed
140407 23:06:14  innobackupex: completed OK!
[root@MySQL-01 ~]#

上面的过程中处理过,主要看最后是否提示innobackupex completed ok,可以看见备份成功。我们看看/data/backup目录下产生了什么复制代码

[root@MySQL-01 backup]# pwd
/data/backup
[root@MySQL-01 backup]# ll
total 4
drwxr-xr-x 12 root root 4096 Apr  7 23:06 2014-04-07_23-05-04
[root@MySQL-01 backup]# cd 2014-04-07_23-05-04/
[root@MySQL-01 2014-04-07_23-05-04]# ll
total 845888
-rw-r--r-- 1 root root       261 Apr  7 23:05 backup-my.cnf
drwx------ 2 root root      4096 Apr  7 23:06 employees
drwx------ 2 root root      4096 Apr  7 23:06 host
-rw-r----- 1 root root 866123776 Apr  7 23:05 ibdata1
drwx------ 2 root root      4096 Apr  7 23:06 menagerie
drwxr-xr-x 2 root root      4096 Apr  7 23:06 mysql
drwxr-xr-x 2 root root      4096 Apr  7 23:06 performance_schema
drwx------ 2 root root      4096 Apr  7 23:06 sakila
drwx------ 2 root root      4096 Apr  7 23:06 test
drwx------ 2 root root      4096 Apr  7 23:06 world_innodb
drwxr-xr-x 2 root root      4096 Apr  7 23:06 world_myisam
-rw-r--r-- 1 root root        13 Apr  7 23:06 xtrabackup_binary
-rw-r--r-- 1 root root        24 Apr  7 23:06 xtrabackup_binlog_info
-rw-r----- 1 root root        95 Apr  7 23:06 xtrabackup_checkpoints
-rw-r----- 1 root root      2560 Apr  7 23:06 xtrabackup_logfile
drwx------ 2 root root      4096 Apr  7 23:06 yayun
[root@MySQL-01 2014-04-07_23-05-04]#

可以看见有对应数据库的名字,比如yayun,还有一个以时间戳命名的目录。我们看看对应文件里面的内容,这几个比较重要

[root@MySQL-01 2014-04-07_23-05-04]# cat xtrabackup_checkpoints 
backup_type = full-backuped
from_lsn = 0
to_lsn = 5324782783
last_lsn = 5324782783
compact = 0
[root@MySQL-01 2014-04-07_23-05-04]# cat xtrabackup_binlog_info 
mysql-bin.000014        2983
[root@MySQL-01 2014-04-07_23-05-04]#

可以看见相关文件记录了LSN,日志偏移量,还可以看见这次是全备份,相信聪明的童鞋们一眼就看懂了。^_^

删除数据库,然后恢复全备(线上不要这样搞)

mysql> drop database yayun;
Query OK, 1 row affected (0.04 sec)

mysql>

恢复全备

恢复备份到mysql的数据文件目录,这一过程要先关闭mysql数据库,重命名或者删除原数据文件目录都可以,再创建一个新的数据文件目录,将备份数据复制到新的数据文件目录下,赋权,修改权限,启动数据库

[root@MySQL-01 ~]# /etc/init.d/mysqld stop
Shutting down MySQL.....                                   [  OK  ]
[root@MySQL-01 ~]# mv /data/mysql /data/mysql_bak
[root@MySQL-01 ~]# mkdir /data/mysql
[root@MySQL-01 ~]#
[root@MySQL-01 ~]# innobackupex --apply-log /data/backup/2014-04-07_23-05-04/ 
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
140407 23:22:36  InnoDB: Starting shutdown...
140407 23:22:40  InnoDB: Shutdown completed; log sequence number 5324784140
140407 23:22:40  innobackupex: completed OK!

以上对应的目录就是innobackupex全备份自己创建的目录。

[root@MySQL-01 ~]# innobackupex --defaults-file=/etc/my.cnf --copy-back --rsync /data/backup/2014-04-07_23-05-04/
innobackupex: Starting to copy InnoDB log files
innobackupex: in '/data/backup/2014-04-07_23-05-04'
innobackupex: back to original InnoDB log directory '/data/mysql'
innobackupex: Copying '/data/backup/2014-04-07_23-05-04/ib_logfile1' to '/data/mysql/ib_logfile1'
innobackupex: Copying '/data/backup/2014-04-07_23-05-04/ib_logfile0' to '/data/mysql/ib_logfile0'
innobackupex: Finished copying back files.
140407 23:27:38  innobackupex: completed OK!
[root@MySQL-01 ~]#

可以看见已经成功恢复,修改数据目录权限,启动mysql,效验数据是否正常,查看yayun库下面的t1表中的数据。

[root@MySQL-01 ~]# chown -R mysql.mysql /data/mysql
[root@MySQL-01 ~]# /etc/init.d/mysqld start
Starting MySQL.................                            [  OK  ]
[root@MySQL-01 ~]#
mysql> use yayun
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from t1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | yayun |
|    2 | atlas |
+------+-------+
2 rows in set (0.00 sec)

mysql>

发现数据已经成功恢复。

3、增量备份

在进行增量备份时,首先要进行一次全量备份,第一次增量备份是基于全备的,之后的增量备份是基于上一次的增量备份,以此类推。

全备份放在/data/backup/full,增量备份放在/data/backup/incremental

[root@MySQL-01 ~]# tree /data/backup/
/data/backup/
├── full
└── incremental

2 directories, 0 files
[root@MySQL-01 ~]#

废话少说,咱们先来一次全备份

[root@MySQL-01 ~]# innobackupex --user=backup --password=yayun --socket=/tmp/mysqld.sock --defaults-file=/etc/my.cnf /data/backup/full/
innobackupex: Backup created in directory '/data/backup/full/2014-04-07_23-37-20'
innobackupex: MySQL binlog position: filename 'mysql-bin.000001', position 107
140407 23:38:29  innobackupex: Connection to database server closed
140407 23:38:29  innobackupex: completed OK!
[root@MySQL-01 ~]#

为了测试效果,我们在t1表中插入数据

mysql> select * from t1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | yayun |
|    2 | atlas |
+------+-------+
2 rows in set (0.00 sec)

mysql> insert into t1 select 1,'love sql';
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> select * from t1;                  
+------+----------+
| id   | name     |
+------+----------+
|    1 | yayun    |
|    2 | atlas    |
|    1 | love sql |
+------+----------+
3 rows in set (0.00 sec)

mysql>

现在来一次增量备份1

[root@MySQL-01 ~]# innobackupex --user=backup --password=yayun --socket=/tmp/mysqld.sock --defaults-file=/etc/my.cnf --incremental /data/backup/incremental/ --incremental-basedir=/data/backup/full/2014-04-07_23-37-20/ --parallel=2
innobackupex: Backup created in directory '/data/backup/incremental/2014-04-07_23-42-46'
innobackupex: MySQL binlog position: filename 'mysql-bin.000001', position 301
140407 23:43:25  innobackupex: Connection to database server closed
140407 23:43:25  innobackupex: completed OK!
[root@MySQL-01 ~]#

我们看看增量备份的大小以及文件内容

[root@MySQL-01 ~]# du -sh /data/backup/full/2014-04-07_23-37-20/
1.2G    /data/backup/full/2014-04-07_23-37-20/
[root@MySQL-01 ~]# du -sh /data/backup/incremental/2014-04-07_23-42-46/
3.6M    /data/backup/incremental/2014-04-07_23-42-46/
[root@MySQL-01 ~]#

看见增量备份的数据很小吧,就是备份改变的数据而已。

[root@MySQL-01 2014-04-07_23-42-46]# pwd
/data/backup/incremental/2014-04-07_23-42-46
[root@MySQL-01 2014-04-07_23-42-46]# cat xtrabackup_checkpoints 
backup_type = incremental
from_lsn = 5324784718
to_lsn = 5324785066
last_lsn = 5324785066
compact = 0
[root@MySQL-01 2014-04-07_23-42-46]#

上面已经明显说明是增量备份了,该工具很人性化吧,呵呵

我们再次向t1表插入数据,然后创建增量备份2

mysql> select * from t1;
+------+----------+
| id   | name     |
+------+----------+
|    1 | yayun    |
|    2 | atlas    |
|    1 | love sql |
+------+----------+
3 rows in set (0.00 sec)

mysql> insert into t1 select 1,'mysql dba';
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> select * from t1;                   
+------+-----------+
| id   | name      |
+------+-----------+
|    1 | yayun     |
|    2 | atlas     |
|    1 | love sql  |
|    1 | mysql dba |
+------+-----------+
4 rows in set (0.00 sec)

mysql>

创建增量备份2(这次是基于上次的增量备份哦)

[root@MySQL-01 ~]# innobackupex --user=backup --password=yayun --socket=/tmp/mysqld.sock --defaults-file=/etc/my.cnf --incremental /data/backup/incremental/ --incremental-basedir=/data/backup/incremental/2014-04-07_23-42-46/ --parallel=2
innobackupex: Backup created in directory '/data/backup/incremental/2014-04-07_23-51-15'
innobackupex: MySQL binlog position: filename 'mysql-bin.000001', position 496
140407 23:51:55  innobackupex: Connection to database server closed
140407 23:51:55  innobackupex: completed OK!
[root@MySQL-01 ~]#

[root@MySQL-01 ~]# ls -ltr /data/backup/full/
total 4
drwxr-xr-x 12 root root 4096 Apr  7 23:38 2014-04-07_23-37-20
[root@MySQL-01 ~]# ls -ltr /data/backup/incremental/
total 8
drwxr-xr-x 12 root root 4096 Apr  7 23:43 2014-04-07_23-42-46
drwxr-xr-x 12 root root 4096 Apr  7 23:51 2014-04-07_23-51-15
[root@MySQL-01 ~]#

4、增量备份恢复

增量备份的恢复大体为3个步骤

  • 恢复完全备份

  • 恢复增量备份到完全备份(开始恢复的增量备份要添加–redo-only参数,到最后一次增量备份去掉–redo-only参数)

  • 对整体的完全备份进行恢复,回滚那些未提交的数据

恢复完全备份(注意这里一定要加–redo-only参数,该参数的意思是只应用xtrabackup日志中已提交的事务数据,不回滚还未提交的数据)

[root@MySQL-01 ~]# innobackupex --apply-log --redo-only /data/backup/full/2014-04-07_23-37-20/
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
140407 23:59:43  InnoDB: Starting shutdown...
140407 23:59:43  InnoDB: Shutdown completed; log sequence number 5324784718
140407 23:59:43  innobackupex: completed OK!

将增量备份1应用到完全备份

[root@MySQL-01 ~]# innobackupex --apply-log --redo-only /data/backup/full/2014-04-07_23-37-20/ --incremental-dir=/data/backup/incremental/2014-04-07_23-42-46/
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-42-46/mysql/func.frm' to '/data/backup/full/2014-04-07_23-37-20/mysql/func.frm'
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-42-46/mysql/help_relation.frm' to '/data/backup/full/2014-04-07_23-37-20/mysql/help_relation.frm'
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-42-46/mysql/help_category.MYD' to '/data/backup/full/2014-04-07_23-37-20/mysql/help_category.MYD'
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-42-46/mysql/ndb_binlog_index.frm' to '/data/backup/full/2014-04-07_23-37-20/mysql/ndb_binlog_index.frm'
140408 00:02:07  innobackupex: completed OK!
[root@MySQL-01 ~]#

将增量备份2应用到完全备份(注意恢复最后一个增量备份时需要去掉–redo-only参数,回滚xtrabackup日志中那些还未提交的数据)

[root@MySQL-01 ~]# innobackupex --apply-log /data/backup/full/2014-04-07_23-37-20/ --incremental-dir=/data/backup/incremental/2014-04-07_23-51-15/
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-51-15/mysql/help_relation.frm' to '/data/backup/full/2014-04-07_23-37-20/mysql/help_relation.frm'
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-51-15/mysql/help_category.MYD' to '/data/backup/full/2014-04-07_23-37-20/mysql/help_category.MYD'
innobackupex: Copying '/data/backup/incremental/2014-04-07_23-51-15/mysql/ndb_binlog_index.frm' to '/data/backup/full/2014-04-07_23-37-20/mysql/ndb_binlog_index.frm'
140408 00:04:33  innobackupex: completed OK!
[root@MySQL-01 ~]#

把所有合在一起的完全备份整体进行一次apply操作,回滚未提交的数据:

[root@MySQL-01 ~]# innobackupex --apply-log /data/backup/full/2014-04-07_23-37-20/
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
140408  0:06:32  InnoDB: Starting shutdown...
140408  0:06:36  InnoDB: Shutdown completed; log sequence number 5324785676
140408 00:06:36  innobackupex: completed OK!

把恢复完的备份复制到数据库目录文件中,赋权,然后启动mysql数据库,检测数据正确性

[root@MySQL-01 ~]# /etc/init.d/mysqld stop
Shutting down MySQL.                                       [  OK  ]
[root@MySQL-01 ~]# mv /data/mysql /data/mysql_bak
[root@MySQL-01 ~]# mkdir /data/mysql
[root@MySQL-01 ~]# innobackupex --defaults-file=/etc/my.cnf --copy-back --rsync /data/backup/full/2014-04-07_23-37-20/
innobackupex: Starting to copy InnoDB log files
innobackupex: in '/data/backup/full/2014-04-07_23-37-20'
innobackupex: back to original InnoDB log directory '/data/mysql'
innobackupex: Copying '/data/backup/full/2014-04-07_23-37-20/ib_logfile1' to '/data/mysql/ib_logfile1'
innobackupex: Copying '/data/backup/full/2014-04-07_23-37-20/ib_logfile0' to '/data/mysql/ib_logfile0'
innobackupex: Finished copying back files.
140408 00:12:42  innobackupex: completed OK!
[root@MySQL-01 ~]# chown -R mysql.mysql /data/mysql
[root@MySQL-01 ~]# /etc/init.d/mysqld start
Starting MySQL....                                         [  OK  ]
[root@MySQL-01 ~]#

查看数据是否正确

mysql> select * from t1;
+------+-----------+
| id   | name      |
+------+-----------+
|    1 | yayun     |
|    2 | atlas     |
|    1 | love sql  |
|    1 | mysql dba |
+------+-----------+
4 rows in set (0.00 sec)

mysql>

5、克隆slave

在日常工作中,我们有时候需要在线添加从库,比如线上有一主一从两个数据库,但是由于业务的需要,一台从库的读取无法满足现在的需求,这样就需要我们在线添加从库,由于出于安全考虑,我们通常需要在从库上进行在线克隆slave。

克隆slave时,常用参数–slave-info和–safe-slave-backup。

–slave-info会将master的binlog文件名和偏移量位置保存到xtrabackup_slave_info文件中

–safe-slave-backup会暂停slave的SQL线程直到没有打开的临时表的时候开始备份。备份结束后SQL线程会自动启动,这样操作的目的主要是确保一致性的复制状态。

下面的例子,将介绍一主一从情况下在线搭建新的从库,环境如下:

master 192.168.0.10    #主库

slave    192.168.0.20    #从库

newslave 192.168.0.100 # 新的从库

在上述示例中,newslave即为要新搭建的从库。在老的从库上面进行备份:

[root@MySQL-02 ~]# innobackupex --user=root --password=12345 --socket=/tmp/mysqld.sock --defaults-file=/etc/my.cnf --slave-info --safe-slave-backup --no-timestamp /data/cloneslave
innobackupex: Backup created in directory '/data/cloneslave'
innobackupex: MySQL binlog position: filename 'mysql-bin.000022', position 107
innobackupex: MySQL slave binlog position: master host '192.168.0.10', filename 'mysql-bin.000006', position 732
140413 23:25:13  innobackupex: completed OK!

这里的/data/cloneslave 目录要不存在,如果存在是会报错的。

查看目录下生成的文件:

[root@MySQL-02 ~]# ll /data/cloneslave/
total 26668
-rw-r--r-- 1 root root      261 Apr 13 23:24 backup-my.cnf
-rw-r--r-- 1 root root 27262976 Apr 13 23:24 ibdata1
drwxr-xr-x 2 root root     4096 Apr 13 23:25 mysql
drwxr-xr-x 2 root root     4096 Apr 13 23:25 performance_schema
drwxr-xr-x 2 root root     4096 Apr 13 23:25 sakila
drwxr-xr-x 2 root root     4096 Apr 13 23:25 world_innodb
-rw-r--r-- 1 root root       13 Apr 13 23:25 xtrabackup_binary
-rw-r--r-- 1 root root       23 Apr 13 23:25 xtrabackup_binlog_info
-rw-r--r-- 1 root root       79 Apr 13 23:25 xtrabackup_checkpoints
-rw-r--r-- 1 root root     2560 Apr 13 23:25 xtrabackup_logfile
-rw-r--r-- 1 root root       72 Apr 13 23:25 xtrabackup_slave_info
drwxr-xr-x 2 root root     4096 Apr 13 23:25 yayun
[root@MySQL-02 ~]#

查看xtrabackup_slave_info文件内容,这个内容就是为搭建从库时需要change master to的参数:

[root@MySQL-02 ~]# cat /data/cloneslave/xtrabackup_slave_info 
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000006', MASTER_LOG_POS=732
[root@MySQL-02 ~]#

在老的slave服务器上进行还原,即192.168.0.20

[root@MySQL-02 ~]# innobackupex --apply-log --redo-only /data/cloneslave/
xtrabackup: starting shutdown with innodb_fast_shutdown = 1
140413 23:30:37  InnoDB: Starting shutdown...
140413 23:30:37  InnoDB: Shutdown completed; log sequence number 12981048
140413 23:30:37  innobackupex: completed OK!
[root@MySQL-02 ~]#

将还原的文件复制到新的从库newslave,即192.168.0.100

[root@MySQL-02 data]# rsync -avprP -e ssh /data/cloneslave/ 192.168.0.100:/data/mysql/

在主库master上添加对新从库newslave的授权:

mysql> grant replication slave on *.* to 'repl'@'192.168.0.100' identified by '123456';
Query OK, 0 rows affected (0.00 sec)

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

mysql>

拷贝老的从库的配置文件到新的从库newslave,并且修改server-id参数,修改完毕后,启动新的从库;

[root@MySQL-02 data]# scp /etc/my.cnf 192.168.0.100:/etc/
 [email protected]'s password: 
 my.cnf                                                                                                             100% 4881     4.8KB/s   00:00 
[root@MySQL-02 data]#
[root@newslave mysql]# egrep 'log-slave|^server-id|skip_slave' /etc/my.cnf 
server-id       = 3
skip_slave_start
log-slave-updates=1
[root@newslave mysql]#
[root@newslave mysql]# chown -R mysql.mysql .
[root@newslave mysql]# /etc/init.d/mysqld restart
Shutting down MySQL.                                       [  OK  ]
Starting MySQL..                                           [  OK  ]
[root@newslave mysql]#

查找老的从库备份后生成的xtrabackup_slave_info文件,提取其中的master_log_file和master_log_pos信息,然后在新的从库上进行change master to操作:

在新的从库上进行同步:

mysql> CHANGE MASTER TO MASTER_HOST='192.168.0.10',MASTER_USER='repl', MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000006', MASTER_LOG_POS=732;
Query OK, 0 rows affected (0.09 sec)

mysql>

启动io线程和sql线程,并观察复制是否正常:

mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> show slave  statusG
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.0.10
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 2
              Master_Log_File: mysql-bin.000006
          Read_Master_Log_Pos: 1309
               Relay_Log_File: MySQL-02-relay-bin.000002
                Relay_Log_Pos: 830
        Relay_Master_Log_File: mysql-bin.000006
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: yayun.%
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1309
              Relay_Log_Space: 989
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
1 row in set (0.00 sec)

mysql>

查看主库,发现已经有两个线程(Binlog Dump)

mysql> show processlistG
*************************** 1. row ***************************
     Id: 8
   User: slave
   Host: 192.168.0.20:44251
     db: NULL
Command: Binlog Dump
   Time: 1088
  State: Master has sent all binlog to slave; waiting for binlog to be updated
   Info: NULL
*************************** 2. row ***************************
     Id: 9
   User: root
   Host: localhost
     db: yayun
Command: Query
   Time: 0
  State: NULL
   Info: show processlist
*************************** 3. row ***************************
     Id: 10
   User: repl
   Host: 192.168.0.100:45844
     db: NULL
Command: Binlog Dump
   Time: 124
  State: Master has sent all binlog to slave; waiting for binlog to be updated
   Info: NULL
3 rows in set (0.00 sec)

mysql>

正常工作,到此在线克隆slave就结束啦。

Ubuntu14.04系统安装配置Nginx+PHP+MySQL+EduSoho

注意: 本教程基于Ubuntu系统 14.04版本并且需要以root用户操作机器,请在确保您的环境是否符合以上两个条件之后,再进行后续的操作。

切记:我们的edusoho访问目录是edusoho/web,只有在nginx或者Apache配
置里面把根目录定位到web目录下,才不会出现奇怪的问题,否则就会出现图片不能显示或者文件无法上传的问题。

本教程主要分为一下几个步骤:更新系统、安装并配置Nginx、安装MySQL、安装并配置PHP、 安装并配置EduSoho,测试Edusoho。

一、更新系统

sudo apt-get update
sudo apt-get upgrade

二、安装Nginx

1、安装nginx

sudo apt-get install nginx

2、配置Nginx

sudo vim /etc/nginx/nginx.conf 
然后 在http{} 字段里添加
client_max_body_size 1024M;

三、安装并配置MySQL

1、安装mysql

apt-get install mysql-server

在这个过程过程中会要求您输入MySQL数据库的root密码,请认真填写。

2、创建数据库

mysql -uroot -p 

然后你需要输入MySQL数据库的root密码。

进入数据库命令行模式后,创建edusoho数据库,执行:

CREATE DATABASE `edusoho` DEFAULT CHARACTER SET utf8 ; 
GRANT ALL PRIVILEGES ON `edusoho`.* TO 'esuser'@'localhost' IDENTIFIED BY 'edusoho';
quit;

注意:这里为edusoho数据库创建了一个用户名,用户名为:esuser,密码为edusoho,在后面安装的第三步需要用到,不建议直接填写root账户。

四、安装PHP

1、安装PHP

sudo apt-get install php5 php5-cli php5-curl php5-fpm php5-intl php5-mcrypt php5-mysqlnd php5-gd

2、修改PHP上传文件的大小限制

sudo vim /etc/php5/fpm/php.ini

添加一下三行配置文字

post_max_size = 1024M 
memory_limit = 1024M
upload_max_filesize = 1024M

3、配置PHP-FPM

编辑配置文件: vim /etc/php5/fpm/pool.d/www.conf,找到:

;listen.owner = www-data
;listen.group = www-data
;listen.mode = 0660
去除最前面的分号。

然后重启PHP-FPM:
sudo /etc/init.d/php5-fpm restart

五、配置安装EduSoho

1、下载/解压程序

mkdir /var/www
cd /var/www
wget http://download.edusoho.com/edusoho-VERSION.tar.gz  (注:将VERSION替换为当前EduSoho最新版本号,可从官网www.edusoho.com查询获取)
tar -zxvf edusoho-VERSION.tar.gz
chown www-data:www-data edusoho/ -Rf
注:这里的下载地址可以在http://www.edusoho.com/中可以获取到,VERSION为下载来之后压缩包后面的版本号。

2、配置nginx的虚拟主机

sudo vim /etc/nginx/sites-enabled/edusoho

输入以下内容:

server {
    listen 80;

    # [改] 网站的域名
    server_name www.example.com example.com;

    #301跳转可以在nginx中配置

    # 程序的安装路径
    root /var/www/edusoho/web;

    # 日志路径
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    location / {
        index app.php;
        try_files $uri @rewriteapp;
    }

    location @rewriteapp {
        rewrite ^(.*)$ /app.php/$1 last;
    }

    location ~ ^/udisk {
        internal;
        root /var/www/edusoho/app/data/;
    }

    location ~ ^/(app|app_dev).php(/|$) {
        fastcgi_pass   unix:/var/run/php5-fpm.sock;
        fastcgi_split_path_info ^(.+.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  HTTPS              off;
        fastcgi_param HTTP_X-Sendfile-Type X-Accel-Redirect;
        fastcgi_param HTTP_X-Accel-Mapping /udisk=/var/www/edusoho/app/data/udisk;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 8 128k;
    }

    # 配置设置图片格式文件
    location ~* .(jpg|jpeg|gif|png|ico|swf)$ {
        # 过期时间为3年
        expires 3y;

        # 关闭日志记录
        access_log off;

        # 关闭gzip压缩,减少CPU消耗,因为图片的压缩率不高。
        gzip off;
    }

    # 配置css/js文件
    location ~* .(css|js)$ {
        access_log off;
        expires 3y;
    }

    # 禁止用户上传目录下所有.php文件的访问,提高安全性
    location ~ ^/files/.*.(php|php5)$ {
        deny all;
    }

    # 以下配置允许运行.php的程序,方便于其他第三方系统的集成。
    location ~ .php$ {
        # [改] 请根据实际php-fpm运行的方式修改
        fastcgi_pass   unix:/var/run/php5-fpm.sock;
        fastcgi_split_path_info ^(.+.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_param  HTTPS              off;
        fastcgi_param  HTTP_PROXY         "";
    }
}

配置文件中的example.com 为您需要指定的域名,为了确保您的域名能被他人访问得到, 您需要将这个域名和您当前的机器IP地址做DNS解析工作。

3、重启nginx

sudo /etc/init.d/nginx restart

最后一步:

浏览器中打开:http://YOU_DOMAIN 安装,当然这里的YOU_DOMAIN是您的域名。

MySQL忘记root密码重置步骤

刚在Linux装完MySQL,然而登录的时候发现不知道root密码。更坑爹的是这个版本没有生成my.cnf的文件。所以不知道如何设置登录的时候跳过密码,so google 了一下,发现可以用安全模式登录修改。操作如下:

$mysqld_safe --skip-grant-tables
> mysql
> use mysql
> update user set authentication_string = PASSWORD('123456') where user = 'root';
flush privileges;

然后用exit,重启mysql,用刚才设置的密码重新登录。

$mysql -u root -p
> show database;

发现报错:ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

> SET PASSWORD = PASSWORD('123456');

MySQL大表性能优化方案

当MySQL单表记录数过大时,增删改查性能都会急剧下降,可以参考以下步骤来优化:

单表优化

除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:

字段

  • 尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
  • VARCHAR的长度只分配真正需要的空间
  • 使用枚举或整数代替字符串类型
  • 尽量使用TIMESTAMP而非DATETIME,
  • 单表不要有太多字段,建议在20以内
  • 避免使用NULL字段,很难查询优化且占用额外索引空间
  • 用整型来存IP

索引

  • 索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
  • 应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 值分布很稀少的字段不适合建索引,例如”性别”这种只有两三个值的字段
  • 字符字段只建前缀索引
  • 字符字段最好不要做主键
  • 不用外键,由程序保证约束
  • 尽量不用UNIQUE,由程序保证约束
  • 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引

查询SQL

  • 可通过开启慢查询日志来找出较慢的SQL
  • 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
  • sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
    不用SELECT *
  • OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
  • 不用函数和触发器,在应用程序实现
  • 避免%xxx式查询
  • 少用JOIN
  • 使用同类型进行比较,比如用’123’和’123’比,123和123比
  • 尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
  • 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
  • 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大

引擎

目前广泛使用的是MyISAM和InnoDB两种引擎:

MyISAM

MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

  • 不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
  • 不支持事务
  • 不支持外键
  • 不支持崩溃后的安全恢复
  • 在表有读取查询的同时,支持往表中插入新纪录
  • 支持BLOB和TEXT的前500个字符索引,支持全文索引
  • 支持延迟更新索引,极大提升写入性能
  • 对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用

InnoDB

InnoDB在MySQL 5.5后成为默认索引,它的特点是:

  • 支持行锁,采用MVCC来支持高并发
  • 支持事务
  • 支持外键
  • 支持崩溃后的安全恢复
  • 不支持全文索引

总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表

系统调优参数

可以使用下面几个工具来做基准测试:

  • sysbench:一个模块化,跨平台以及多线程的性能测试工具
  • iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具
  • tpcc-mysql:Percona开发的TPC-C测试工具

具体的调优参数内容较多,具体可参考官方文档,这里介绍一些比较重要的参数:

  • back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500
  • wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时
  • max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限
  • thread_concurrency:并发线程数,设为CPU核数的两倍
  • skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问
  • key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like ‘key_read%’,保证key_reads / key_read_requests在0.1%以下最好
  • innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like ‘Innodb_buffer_pool_read%’,保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好
  • innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小
  • innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB
  • query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大.
    可以通过命令show status like ‘Qcache_%’查看目前系统Query catch使用大小
  • read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能
  • sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小
  • read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
  • record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值
    thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的
  • table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM

升级硬件

Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能

读写分离

也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离

缓存

缓存可以发生在这些层次:

  • MySQL内部:在系统调优参数介绍了相关设置
  • 数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object
  • 应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object
  • Web层:针对web页面做缓存
  • 浏览器客户端:用户端的缓存

可以根据实际情况在一个层次或多个层次结合加入缓存。这里重点介绍下服务层的缓存实现,目前主要有两种方式:

  • 直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。
  • 回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。

表分区

MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码

对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引

未分类

用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,如下图5条记录落在两个分区上:

mysql> explain partitions select count(1) from user_partition where id in (1,2,3,4,5);
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table          | partitions | type  | possible_keys | key     | key_len | ref  | rows | Extra                    |
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
|  1 | SIMPLE      | user_partition | p1,p4      | range | PRIMARY       | PRIMARY | 8       | NULL |    5 | Using where; Using index |
+----+-------------+----------------+------------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

分区的好处是:

  • 可以让单表存储更多的数据
  • 分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
  • 部分查询能够从查询条件确定只落在少数分区上,速度会很快
  • 分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备
  • 可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
  • 可以备份和恢复单个分区

分区的限制和缺点:

  • 一个表最多只能有1024个分区
  • 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
  • 分区表无法使用外键约束
  • NULL值会使分区过滤无效
  • 所有分区必须使用相同的存储引擎

分区的类型:

  • RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
  • LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
  • HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
  • KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值

分区适合的场景有:

  • 最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示:
CREATE TABLE members (
    firstname VARCHAR(25) NOT NULL,
    lastname VARCHAR(25) NOT NULL,
    username VARCHAR(16) NOT NULL,
    email VARCHAR(35),
    joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);

查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。

  • 如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存

另外MySQL有一种早期的简单的分区实现 – 合并表(merge table),限制较多且缺乏优化,不建议使用,应该用新的分区机制来替代

垂直拆分

垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联

比如原始的用户表是:

未分类

垂直拆分后是:

未分类

垂直拆分的优点是:

  • 可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)
  • 可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起
  • 数据维护简单

缺点是:

  • 主键出现冗余,需要管理冗余列
  • 会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力
  • 依然存在单表数据量过大的问题(需要水平拆分)
  • 事务处理复杂

水平拆分

概述

水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的MySQL表或库,达到分布式的效果,能够支持非常大的数据量。前面的表分区本质上也是一种特殊的库内分表

库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决

前面垂直拆分的用户表如果进行水平拆分,结果是:

未分类

实际情况中往往会是垂直拆分和水平拆分的结合,即将Users_A_M和Users_N_Z再拆成Users和UserExtras,这样一共四张表

水平拆分的优点是:

  • 不存在单库大数据和高并发的性能瓶颈
  • 应用端改造较少
  • 提高了系统的稳定性和负载能力

缺点是:

  • 分片事务一致性难以解决
  • 跨节点Join性能差,逻辑复杂
  • 数据多次扩展难度跟维护量极大

分片原则

  • 能不分就不分,参考单表优化
  • 分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量
  • 分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容
  • 尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题
  • 查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
  • 通过数据冗余和表分区赖降低跨库Join的可能

这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。

解决方案

由于水平拆分牵涉的逻辑比较复杂,当前也有了不少比较成熟的解决方案。这些方案分为两大类:客户端架构和代理架构。

客户端架构

通过修改数据访问层,如JDBC、Data Source、MyBatis,通过配置来管理多个数据源,直连数据库,并在模块内完成数据的分片整合,一般以Jar包的方式呈现

这是一个客户端架构的例子:

未分类

可以看到分片的实现是和应用服务器在一起的,通过修改Spring JDBC层来实现

客户端架构的优点是:

  • 应用直连数据库,降低外围系统依赖所带来的宕机风险
  • 集成成本低,无需额外运维的组件

缺点是:

  • 限于只能在数据库访问层上做文章,扩展性一般,对于比较复杂的系统可能会力不从心
  • 将分片逻辑的压力放在应用服务器上,造成额外风险

代理架构

通过独立的中间件来统一管理所有数据源和数据分片整合,后端数据库集群对前端应用程序透明,需要独立部署和运维代理组件

这是一个代理架构的例子:

未分类

代理组件为了分流和防止单点,一般以集群形式存在,同时可能需要Zookeeper之类的服务组件来管理

代理架构的优点是:

  • 能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强
  • 对于应用服务器透明且没有增加任何额外负载

缺点是:

  • 需部署和运维独立的代理中间件,成本高
  • 应用需经过代理来连接数据库,网络上多了一跳,性能有损失且有额外风险

各方案比较

未分类

如此多的方案,如何进行选择?可以按以下思路来考虑:

  1. 确定是使用代理架构还是客户端架构。中小型规模或是比较简单的场景倾向于选择客户端架构,复杂场景或大规模系统倾向选择代理架构

  2. 具体功能是否满足,比如需要跨节点ORDER BY,那么支持该功能的优先考虑

  3. 不考虑一年内没有更新的产品,说明开发停滞,甚至无人维护和技术支持

  4. 最好按大公司->社区->小公司->个人这样的出品方顺序来选择

  5. 选择口碑较好的,比如github星数、使用者数量质量和使用者反馈

  6. 开源的优先,往往项目有特殊需求可能需要改动源代码

按照上述思路,推荐以下选择:

  • 客户端架构:ShardingJDBC
  • 代理架构:MyCat或者Atlas

兼容MySQL且可水平扩展的数据库

目前也有一些开源数据库兼容MySQL协议,如:

  • TiDB
  • Cubrid

但其工业品质和MySQL尚有差距,且需要较大的运维投入,如果想将原始的MySQL迁移到可水平扩展的新数据库中,可以考虑一些云数据库:

  • 阿里云PetaData
  • 阿里云OceanBase
  • 腾讯云DCDB

NoSQL

在MySQL上做Sharding是一种戴着镣铐的跳舞,事实上很多大表本身对MySQL这种RDBMS的需求并不大,并不要求ACID,可以考虑将这些表迁移到NoSQL,彻底解决水平扩展问题,例如:

  • 日志类、监控类、统计类数据
  • 非结构化或弱结构化数据
  • 对事务要求不强,且无太多关联操作的数据

CentOS使用mysqlbinlog恢复MySQL数据库

如果不小心对数据库进行误操作,而又没有及时备份怎么办?这恐怕是广大的coder经常遇到的一类问题。 我今天就因为不小心删除了某个数据库,但最后的备份是1个礼拜前的,唯一能解决的办法就是通过mysqlbinlog来恢复了。解决方案如下:

1、如果MySQL服务器启用了二进制日志,你可以使用mysqlbinlog工具来恢复从指定的时间点开始(例如,从你最后一次备份)直到现在或另一个指定的时间点的数据。

2、要想从二进制日志恢复数据,你需要知道当前二进制日志文件的路径和文件名。

3、一般可以从配置文件(一般情况,Linux下为my.cnf ,windows系统下为my.ini,取决于你的系统)中找到路径。如果未包含在选项文件中,当服务器启动时,可以在命令行中以选项的形式给出。

4、启用二进制日志的选项为–log-bin。

5、要想确定当前的二进制日志文件的文件名,输入下面的MySQL语句:

SHOW BINLOG EVENTS G;
或者还可以从命令行输入下面的内容:
mysql –user=root -pmypasswd -e ‘SHOW BINLOG EVENTS G’ 将密码mypasswd替换为你的MySQL服务器的root密码。

6、比如得到的日志文件名为:

mysql-bin.000001 1. 指定恢复时间

对于MySQL5.1.54,可以在mysqlbinlog语句中通过–start-date和–stop-date选项指定DATETIME格式的起止时间。

7、举例说明,比如在今天下午14:02(今天是2012年3月15日),不小心执行SQL语句删除了一个数据表,但发现没有最新的备份(当然,这只是开发环境,并不是正式的生产环境,正式环境还得定时做数据备份)。要想恢复表和数据,可以通过mysqlbinlog恢复指定时间的备份,输入:

mysqlbinlog –stop-date=”2012-03-15 14:02:00″ /data1/log/mysql/mysql-bin.000001  | mysql -u root -pmypasswd

该命令将恢复截止到在–stop-date选项中以DATETIME格式给出的日期和时间的所有数据。

8、如果你没有检测到输入的错误的SQL语句,可能你想要恢复后面发生的数据库活动。 根据这些,你可以用起使日期和时间再次运行mysqlbinlog:

mysqlbinlog –start-date=”2012-03-15 00:01:00″ /data1/log/mysql/mysql-bin.000001  | mysql -u root -pmypasswd

9、在该行中,从今天凌晨0:01登录的SQL语句将运行,组合执行前夜的转储文件和mysqlbinlog的两行可以将所有数据恢复到今天凌晨0:01前一秒钟。 你应检查日志以确保时间确切。

10、和–stop-date恢复指定时间段的数据库活动记录,如下:

mysqlbinlog –start-date=”2012-03-09 02:00:00″ –stop-date=”2012-03-15 14:00:00″ /data1/log/mysql/mysql-bin.000001 > /tmp/mysql_restore_030915.sql

通过这种方式,就能获取最后一个备份的文件时间2012-03-09 02:00:00到今天删除数据库之前2012-03-15 14:02这段时间的数据库活动事务操作

MySQL修改账号授权的的IP地址

今天遇到一个需求:修改MySQL用户的权限,需要限制特定IP地址才能访问,第一次遇到这类需求,结果在测试过程,使用更新系统权限报发现出现了一些问题, 具体演示如下. 下面测试环境为MySQL 5.6.20. 如有其它版本与下面测试结果有出入,请以实际环境为准。

我们先创建一个测试用户LimitIP,只允许192.168段的IP地址访问,具体权限如下所示:

mysql> GRANT SELECT ON MyDB.* TO LimitIP@'192.168.%' IDENTIFIED BY 'LimitIP';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT INSERT ,UPDATE,DELETE ON MyDB.kkk TO LimitIP@'192.168.%';
Query OK, 0 rows affected (0.00 sec)

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

mysql> 

mysql> show grants for LimitIP@'192.168.%';
+----------------------------------------------------------------------------------------------------------------+
| Grants for [email protected].%                                                                                   |
+----------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'LimitIP'@'192.168.%' IDENTIFIED BY PASSWORD '*72DDE03E02CC55A9478A82F3F4EBE7F639249DEC' |
| GRANT SELECT ON `MyDB`.* TO 'LimitIP'@'192.168.%'                                                              |
| GRANT INSERT, UPDATE, DELETE ON `MyDB`.`kkk` TO 'LimitIP'@'192.168.%'                                          |
+----------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql>

未分类

假设现在收到需求:这个用户只允许这个IP地址192.168.103.17访问,于是我打算更新mysql.user表,如下所示:

mysql> select user, host from mysql.user where user='LimitIP';
+---------+-----------+
| user    | host      |
+---------+-----------+
| LimitIP | 192.168.% |
+---------+-----------+
1 row in set (0.00 sec)

mysql> update mysql.user set host='192.168.103.17' where user='LimitIP';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

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

mysql> select user, host from user where user='LimitIP';
ERROR 1046 (3D000): No database selected
mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select user, host from user where user='LimitIP';
+---------+----------------+
| user    | host           |
+---------+----------------+
| LimitIP | 192.168.103.17 |
+---------+----------------+
1 row in set (0.00 sec)

mysql> show grants for LimitIP@'192.168.103.17';
+---------------------------------------------------------------------------------------------------------------------+
| Grants for [email protected]                                                                                   |
+---------------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'LimitIP'@'192.168.103.17' IDENTIFIED BY PASSWORD '*72DDE03E02CC55A9478A82F3F4EBE7F639249DEC' |
+---------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> 

未分类

上面测试发现,如果这样只修改mysql.user表, 那么之前的权限没有了,如下所示,如果你查询mysql.db、 mysql.tables_priv 发现Host的字段值依然为192.168.%

mysql>  select * from mysql.db where user='LimitIP'G;
*************************** 1. row ***************************
                 Host: 192.168.%
                   Db: MyDB
                 User: LimitIP
          Select_priv: Y
          Insert_priv: N
          Update_priv: N
          Delete_priv: N
          Create_priv: N
            Drop_priv: N
           Grant_priv: N
      References_priv: N
           Index_priv: N
           Alter_priv: N
Create_tmp_table_priv: N
     Lock_tables_priv: N
     Create_view_priv: N
       Show_view_priv: N
  Create_routine_priv: N
   Alter_routine_priv: N
         Execute_priv: N
           Event_priv: N
         Trigger_priv: N
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql> select * from mysql.tables_priv where user='LimitIP'G;
*************************** 1. row ***************************
       Host: 192.168.%
         Db: MyDB
       User: LimitIP
 Table_name: kkk
    Grantor: root@localhost
  Timestamp: 0000-00-00 00:00:00
 Table_priv: Insert,Update,Delete
Column_priv: 
1 row in set (0.00 sec)

ERROR: 
No query specified

所以我继续修改 mysql.db、 mysql.tables_priv 表,然后测试验证终于OK了(请见下面测试步骤),当然如果账户的权限不止这几个层面,你可能还必须修改例如mysql.columns_priv、mysql.procs_priv等表

mysql> show grants for LimitIP@'192.168.%';
ERROR 1141 (42000): There is no such grant defined for user 'LimitIP' on host '192.168.%'
mysql> 
mysql> 
mysql> update mysql.db set host='192.168.103.17' where user='LimitIP';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update mysql.tables_priv set host='192.168.103.17' where user='LimitIP';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

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

mysql> show grants for LimitIP@'192.168.103.17';
+---------------------------------------------------------------------------------------------------------------------+
| Grants for [email protected]                                                                                   |
+---------------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'LimitIP'@'192.168.103.17' IDENTIFIED BY PASSWORD '*72DDE03E02CC55A9478A82F3F4EBE7F639249DEC' |
| GRANT SELECT ON `MyDB`.* TO 'LimitIP'@'192.168.103.17'                                                              |
| GRANT INSERT, UPDATE, DELETE ON `MyDB`.`kkk` TO 'LimitIP'@'192.168.103.17'                                          |
+---------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql> 

未分类

如果需要修改用户的IP限制,其实更新mysql相关权限表不是上上策,其实有更好的方法,那就是RENAME USER Syntax (https://dev.mysql.com/doc/refman/5.6/en/rename-user.html)

mysql> RENAME USER 'LimitIP'@'192.168.103.17' TO 'LimitIP'@'192.168.103.18';
Query OK, 0 rows affected (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> show grants for 'LimitIP'@'192.168.103.18';
+---------------------------------------------------------------------------------------------------------------------+
| Grants for [email protected]                                                                                   |
+---------------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'LimitIP'@'192.168.103.18' IDENTIFIED BY PASSWORD '*72DDE03E02CC55A9478A82F3F4EBE7F639249DEC' |
| GRANT SELECT ON `MyDB`.* TO 'LimitIP'@'192.168.103.18'                                                              |
| GRANT INSERT, UPDATE, DELETE ON `MyDB`.`kkk` TO 'LimitIP'@'192.168.103.18'                                          |
+---------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.00 sec)

mysql>