深入理解MySQL――锁、事务与并发控制

本文对锁、事务、并发控制做一个总结,看了网上很多文章,描述非常不准确。如有与您观点不一致,欢迎有理有据的拍砖!

本文对锁、事务、并发控制做一个总结,看了网上很多文章,描述非常不准确。如有与您观点不一致,欢迎有理有据的拍砖!

mysql服务器逻辑架构

未分类

每个连接都会在mysql服务端产生一个线程(内部通过线程池管理线程),比如一个select语句进入,mysql首先会在查询缓存中查找是否缓存了这个select的结果集,如果没有则继续执行 解析、优化、执行的过程;否则会之间从缓存中获取结果集。

mysql并发控制——共享锁、排他锁

共享锁

共享锁也称为读锁,读锁允许多个连接可以同一时刻并发的读取同一资源,互不干扰;

排他锁

排他锁也称为写锁,一个写锁会阻塞其他的写锁或读锁,保证同一时刻只有一个连接可以写入数据,同时防止其他用户对这个数据的读写。

锁策略

锁的开销是较为昂贵的,锁策略其实就是保证了线程安全的同时获取最大的性能之间的平衡策略。

mysql锁策略:talbe lock(表锁)

表锁是mysql最基本的锁策略,也是开销最小的锁,它会锁定整个表;

具体情况是:若一个用户正在执行写操作,会获取排他的“写锁”,这可能会锁定整个表,阻塞其他用户的读、写操作;

若一个用户正在执行读操作,会先获取共享锁“读锁”,这个锁运行其他读锁并发的对这个表进行读取,互不干扰。只要没有写锁的进入,读锁可以是并发读取统一资源的。

通常发生在DDL语句DML不走索引的语句中,比如这个DML update table set columnA=”A” where columnB=“B”.
如果columnB字段不存在索引(或者不是组合索引前缀),会锁住所有记录也就是锁表。如果语句的执行能够执行一个columnB字段的索引,那么会锁住满足where的行(行锁)。

mysql锁策略:row lock(行锁)

行锁可以最大限度的支持并发处理,当然也带来了最大开销,顾名思义,行锁的粒度实在每一条行数据。

事务

事务就是一组原子性的sql,或者说一个独立的工作单元。
事务就是说,要么mysql引擎会全部执行这一组sql语句,要么全部都不执行(比如其中一条语句失败的话)。

比如,tim要给bill转账100块钱:

  1. 检查tim的账户余额是否大于100块;
  2. tim的账户减少100块;
  3. bill的账户增加100块;

这三个操作就是一个事务,必须打包执行,要么全部成功,要么全部不执行,其中任何一个操作的失败都会导致所有三个操作“不执行”——回滚。

CREATE DATABASE IF NOT EXISTS employees; 
USE employees; 

CREATE TABLE `employees`.`account` ( 
 `id` BIGINT (11) NOT NULL AUTO_INCREMENT, 
 `p_name` VARCHAR (4), 
 `p_money` DECIMAL (10, 2) NOT NULL DEFAULT 0, 
 PRIMARY KEY (`id`) 
) ; 
INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('1', 'tim', '200');  
INSERT INTO `employees`.`account` (`id`, `p_name`, `p_money`) VALUES ('2', 'bill', '200');  

START TRANSACTION; 
SELECT p_money FROM account WHERE p_name="tim";-- step1 
UPDATE account SET p_money=p_money-100 WHERE p_name="tim";-- step2 
UPDATE account SET p_money=p_money+100 WHERE p_name="bill";-- step3 
COMMIT;

一个良好的事务系统,必须满足ACID特点:

事务的ACID

A:atomiciy原子性 一个事务必须保证其中的操作要么全部执行,要么全部回滚,不可能存在只执行了一部分这种情况出现。

C:consistency一致性 数据必须保证从一种一致性的状态转换为另一种一致性状态。 比如上一个事务中执行了第二步时系统崩溃了,数据也不会出现bill的账户少了100块,但是tim的账户没变的情况。要么维持原装(全部回滚),要么bill少了100块同时tim多了100块,只有这两种一致性状态的

I:isolation隔离性 在一个事务未执行完毕时,通常会保证其他Session 无法看到这个事务的执行结果

D:durability持久性 事务一旦commit,则数据就会保存下来,即使提交完之后系统崩溃,数据也不会丢失。

隔离级别

未分类

查看系统隔离级别: select @@global.tx_isolation; 查看当前会话隔离级别 select @@tx_isolation; 设置当前会话隔离级别 SET session TRANSACTION ISOLATION LEVEL serializable; 设置全局系统隔离级别 SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

READ UNCOMMITTED(未提交读,可脏读)

事务中的修改,即使没有提交,对其他会话也是可见的。
可以读取未提交的数据——脏读。脏读会导致很多问题,一般不适用这个隔离级别。
实例:

-- ------------------------- read-uncommitted实例 ------------------------------ 
-- 设置全局系统隔离级别 
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
-- Session A 
START TRANSACTION; 
SELECT * FROM USER; 
UPDATE USER SET NAME="READ UNCOMMITTED"; 
-- commit; 

-- Session B 
SELECT * FROM USER; 

//SessionB Console 可以看到Session A未提交的事物处理,在另一个Session 中也看到了,这就是所谓的脏读 
id  name 
2   READ UNCOMMITTED 
34  READ UNCOMMITTED

READ COMMITTED(提交读或不可重复读,幻读)

一般数据库都默认使用这个隔离级别(mysql不是),这个隔离级别保证了一个事务如果没有完全成功(commit执行完),事务中的操作对其他会话是不可见的。

-- ------------------------- read-cmmitted实例 ------------------------------ 
-- 设置全局系统隔离级别 
SET GLOBAL TRANSACTION ISOLATION LEVEL READ  COMMITTED; 
-- Session A 
START TRANSACTION; 
SELECT * FROM USER; 
UPDATE USER SET NAME="READ COMMITTED"; 
-- COMMIT; 

-- Session B 
SELECT * FROM USER; 

//Console OUTPUT: 
id  name 
2   READ UNCOMMITTED 
34  READ UNCOMMITTED 


--------------------------------------------------- 
-- 当 Session  A执行了commit,Session B得到如下结果: 
id  name 
2   READ COMMITTED 
34  READ COMMITTED

也就验证了read committed级别在事物未完成commit操作之前修改的数据对其他Session 不可见,执行了commit之后才会对其他Session 可见。
我们可以看到Session B两次查询得到了不同的数据。

read committed隔离级别解决了脏读的问题,但是会对其他Session 产生两次不一致的读取结果(因为另一个Session 执行了事务,一致性变化)。

REPEATABLE READ(可重复读)

一个事务中多次执行统一读SQL,返回结果一样。
这个隔离级别解决了脏读的问题,幻读问题。这里指的是innodb的rr级别,innodb中使用next-key锁对”当前读”进行加锁,锁住行以及可能产生幻读的插入位置,阻止新的数据插入产生幻行。
下文中详细分析。

具体请参考mysql手册

https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html

SERIALIZABLE(可串行化)

最强的隔离级别,通过给事务中每次读取的行加锁,写加写锁,保证不产生幻读问题,但是会导致大量超时以及锁争用问题。

多版本并发控制-MVCC

MVCC(multiple-version-concurrency-control)是个行级锁的变种,它在普通读情况下避免了加锁操作,因此开销更低。
虽然实现不同,但通常都是实现非阻塞读,对于写操作只锁定必要的行。

一致性读 (就是读取快照) select * from table ….;

当前读(就是读取实际的持久化的数据) 特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。 select * from table where ? lock in share mode; select * from table where ? for update; insert; update ; delete;

注意:select …… from where…… (没有额外加锁后缀)使用MVCC,保证了读快照(mysql称为consistent read),所谓一致性读或者读快照就是读取当前事务开始之前的数据快照,在这个事务开始之后的更新不会被读到。详细情况下文select的详述。

对于加锁读SELECT with FOR UPDATE(排他锁) or LOCK IN SHARE MODE(共享锁)、update、delete语句,要考虑是否是唯一索引的等值查询。

写锁-recordLock,gapLock,next key lock

对于使用到唯一索引 等值查询:比如,where columnA=”…” ,如果columnA上的索引被使用到,
那么会在满足where的记录上加行锁(for update是排他锁,lock in shared 是共享锁,其他写操作加排他锁)。这里是行级锁,record lock。

对于范围查询(使用非唯一的索引):
比如(做范围查询):where columnA between 10 and 30 ,会导致其他会话中10以后的数据都无法插入(next key lock),从而解决了幻读问题。

这里是next key lock 会包括涉及到的所有行。
next key lock=recordLock+gapLock,不仅锁住相关数据,而且锁住边界,从而彻底避免幻读。

对于没有索引
锁表
通常发生在DDL语句DML不走索引的语句中,比如这个DML update table set columnA=”A” where columnB=“B”.
如果columnB字段不存在索引(或者不是组合索引前缀),会锁住所有记录也就是锁表。如果语句的执行能够执行一个columnB字段的索引,那么会锁住满足where的行(行锁)。

INNODB的MVCC通常是通过在每行数据后边保存两个隐藏的列来实现(其实是三列,第三列是用于事务回滚,此处略去),
一个保存了行的创建版本号,另一个保存了行的更新版本号(上一次被更新数据的版本号)
这个版本号是每个事务的版本号,递增的。

这样保证了innodb对读操作不需要加锁也能保证正确读取数据。

MVCC select无锁操作 与 维护版本号

下边在mysql默认的Repeatable Read隔离级别下,具体看看MVCC操作:

Select(快照读,所谓读快照就是读取当前事务之前的数据。): a.InnoDB只select查找版本号早于当前版本号的数据行,这样保证了读取的数据要么是在这个事务开始之前就已经commit了的(早于当前版本号),要么是在这个事务自身中执行创建操作的数据(等于当前版本号)。 b.查找行的更新版本号要么未定义,要么大于当前的版本号(为了保证事务可以读到老数据),这样保证了事务读取到在当前事务开始之后未被更新的数据。 注意: 这里的select不能有for update、lock in share 语句。 总之要只返回满足以下条件的行数据,达到了快照读的效果:

(行创建版本号< =当前版本号 && (行更新版本号==null or 行更新版本号>当前版本号 ) )

Insert InnoDB为这个事务中新插入的行,保存当前事务版本号的行作为行的行创建版本号。

Delete InnoDB为每一个删除的行保存当前事务版本号,作为行的删除标记。

Update 将存在两条数据,保持当前版本号作为更新后的数据的新增版本号,同时保存当前版本号作为老数据行的更新版本号。

当前版本号—写—>新数据行创建版本号 && 当前版本号—写—>老数据更新版本号();

脏读 vs 幻读 vs 不可重复读

脏读:一事务未提交的中间状态的更新数据 被其他会话读取到。 当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有 提交到数据库中(commit未执行),这时,另外会话也访问这个数据,因为这个数据是还没有提交, 那么另外一个会话读到的这个数据是脏数据,依据脏数据所做的操作也可能是不正确的。

不可重复读:简单来说就是在一个事务中读取的数据可能产生变化,ReadCommitted也称为不可重复读。

在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一会话事务已提交的更新数据。 相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一会话事务已提交的更新数据。

幻读:会话T1事务中执行一次查询,然后会话T2新插入一行记录,这行记录恰好可以满足T1所使用的查询的条件。然后T1又使用相同 的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行。这个新行就称为“幻像”,因为对T1来说这一行就像突然 出现的一样。
innoDB的RR级别无法做到完全避免幻读,下文详细分析。

----------------------------------前置准备---------------------------------------- 
prerequisite: 
-- 创建表 
mysql> 
CREATE TABLE `t_bitfly` ( 
  `id` bigint(20) NOT NULL DEFAULT '0', 
  `value` varchar(32) DEFAULT NULL, 
  PRIMARY KEY (`id`) 
) 

-- 确保当前隔离级别为默认的RR级别 

mysql> select @@global.tx_isolation, @@tx_isolation; 
+-----------------------+-----------------+ 
| @@global.tx_isolation | @@tx_isolation  | 
+-----------------------+-----------------+ 
| REPEATABLE-READ       | REPEATABLE-READ | 
+-----------------------+-----------------+ 
1 row in set (0.00 sec) 
---------------------------------------开始---------------------------------------------  


session A                                           |   session B 
                                                   | 
                                                   | 
mysql> START TRANSACTION;                           |   mysql> START TRANSACTION; 
Query OK, 0 rows affected (0.00 sec)                |   Query OK, 0 rows affected (0.00 sec)                                         
                                                    |    
                                                   | 
mysql> SELECT * FROM test.t_bitfly;                 |   mysql> SELECT * FROM test.t_bitfly;  
Empty set (0.00 sec)                                |   Empty set (0.00 sec) 
                                                   | 
                                                   |   mysql> INSERT INTO t_bitfly VALUES (1, 'test'); 
                                                    |   Query OK, 1 row affected (0.00 sec) 
                                                   | 
                                                   | 
mysql> SELECT * FROM test.t_bitfly;                 | 
Empty set (0.00 sec)                                | 
                                                   | 
                                                   |   mysql> commit; 
                                                   |   Query OK, 0 rows affected (0.01 sec)                                                 
mysql> SELECT * FROM test.t_bitfly;                 | 
Empty set (0.00 sec)                                | 
-- 可以看到虽然两次执行结果返回的数据一致,         | 
-- 但是不能说明没有幻读。接着看:                   | 
                                                   | 
mysql> INSERT INTO t_bitfly VALUES (1, 'test');     | 
ERROR 1062 (23000):                                 | 
Duplicate entry '1' for key 'PRIMARY'               | 
                                                   | 
-- 明明为空的表,为什么说主键重复?——幻读出现 !!!       |

如何保证rr级别绝对不产生幻读?

在使用的select …where语句中加入 for update(排他锁) 或者 lock in share mode(共享锁)语句来实现。其实就是锁住了可能造成幻读的数据,阻止数据的写入操作。

其实是因为数据的写入操作(insert 、update)需要先获取写锁,由于可能产生幻读的部分,已经获取到了某种锁,所以要在另外一个会话中获取写锁的前提是当前会话中释放所有因加锁语句产生的锁。

mysql死锁问题

死锁,就是产生了循环等待链条,我等待你的资源,你却等待我的资源,我们都相互等待,谁也不释放自己占有的资源,导致无线等待下去。
比如:

//Session A 
START TRANSACTION; 
UPDATE account SET p_money=p_money-100 WHERE p_name="tim"; 
UPDATE account SET p_money=p_money+100 WHERE p_name="bill"; 
COMMIT; 
//Thread B 
START TRANSACTION; 
UPDATE account SET p_money=p_money+100 WHERE p_name="bill"; 
UPDATE account SET p_money=p_money-100 WHERE p_name="tim"; 
COMMIT;

当线程A执行到第一条语句UPDATE account SET p_money=p_money-100 WHERE p_name=”tim”;锁定了p_name=”tim”的行数据;并且试图获取p_name=”bill”的数据;

,此时,恰好,线程B也执行到第一条语句:UPDATE account SET p_money=p_money+100 WHERE p_name=”bill”;

锁定了 p_name=”bill”的数据,同时试图获取p_name=”tim”的数据;
此时,两个线程就进入了死锁,谁也无法获取自己想要获取的资源,进入无线等待中,直到超时!

innodb_lock_wait_timeout 等待锁超时回滚事务:
直观方法是在两个事务相互等待时,当一个等待时间超过设置的某一阀值时,对其中一个事务进行回滚,另一个事务就能继续执行。这种方法简单有效,在innodb中,参数innodb_lock_wait_timeout用来设置超时时间。

wait-for graph算法来主动进行死锁检测:
innodb还提供了wait-for graph算法来主动进行死锁检测,每当加锁请求无法立即满足需要并进入等待时,wait-for graph算法都会被触发。

如何尽可能避免死锁

1)以固定的顺序访问表和行。比如两个更新数据的事务,事务A 更新数据的顺序 为1,2;事务B更新数据的顺序为2,1。这样更可能会造成死锁。

2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。

3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。

4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。

5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

显式锁 与 隐式锁
隐式锁:我们上文说的锁都属于不需要额外语句加锁的隐式锁。
显示锁:

SELECT … LOCK IN SHARE MODE(加共享锁); SELECT … FOR UPDATE(加排他锁);

详情上文已经说过。

通过如下sql可以查看等待锁的情况

select * from information_schema.innodb_trx where trx_state="lock wait";

show engine innodb status;

mysql中的事务

show variables like "autocommit"; 

set autocommit=0; //0表示AutoCommit关闭 
set autocommit=1; //1表示AutoCommit开启

自动提交(AutoCommit,mysql默认)

mysql默认采用AutoCommit模式,也就是每个sql都是一个事务,并不需要显示的执行事务。

如果autoCommit关闭,那么每个sql都默认开启一个事务,只有显式的执行“commit”后这个事务才会被提交。

Mysql设置编码方式及基本操作

介绍

Mysql 默认安装后的编码方式默认一般是Latin, 在插入汉字数据或读取的时候,存在乱码或报错。这时候,只需要修改编码方式为UTF8,统一数据库和数据表的编码方式。

如何修改

1、 复制MySQL数据库中的配置文件,然后粘贴重命名为my.ini

2、 在文件中添加以下语句。

[mysqld]
Character-set-server = utf8
[client]
Default-character-set = utf8
[mysql]
Default-character-set = utf8

3、 重启查看

Net stop mysql;
Net start mysql;
Mysql -u root -p 
show variables like "% character %";

基本操作

1、 查看编码格式

show variables like 'character_set_database';
show create table <表名>;

2、 指定编码格式

create database <数据库名> character set utf8;
create table tb_books (
    name varchar(45) not null,
    price double not null,
    bookCount int not null,
    author varchar(45) not null ) default charset = utf8;

3、 修改编码格式

alter database <数据库名> character set utf8;
alter table <表名> character set utf8;
alter table <表名> change <字段名> <字段名> <类型> character set utf8;

4、 外键操作

alter table <表名> add constraint <外键名> foreign key<字段名> REFERENCES <外表表名><字段名>;
alter table <表名> drop foreign key <外键名>;

5、 增删改查

Select * from user where name = ‘Jachin’;
Delete  from user where name = ‘Jachin’;
Update user set name = ‘Jachin’ where name = ‘Wang’;
Insert into user (name) values (‘Jachin’);

Linux下配置mysql数据库主从自动备份

当网站内容越来越多的时候,数据库也变得越来越大了。如果不小心误删了,或者被攻击了,那就什么都没有了,一切归零。到现在博主也认识到了数据库的重要性,于是决定研究研究数据库的自动备份。如果你是土豪那可以直接买各种云服务商的数据库,自带容灾备份的,安全性比较好。

本文仅讨论数据库放在本地的情况,备份方式为主从:即一台服务器作为主服务器,另外一台服务器作为备份服务器,当主服务器故障时,可以通过备份服务器来接管/恢复。

先来看看数据库主备备份的原理:

MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主从后,在A上写数据,另外一台B也会跟着写数据,两者数据实时同步的

MySQL主从是基于binlog的,主上须开启binlog才能进行主从。 主从过程大致有3个步骤 

1)主将更改操作记录到binlog里 

2)从将主的binlog事件(sql语句)同步到从本机上并记录在relaylog里 

3)从根据relaylog里面的sql语句按顺序执行

主上有一个log dump线程,用来和从的I/O线程传递binlog

从上有两个线程,其中I/O线程用来同步主的binlog并生成relaylog,另外一个SQL线程用来把relaylog里面的sql语句执行一遍

两种情况:一种是做备份用,一种是作为读用

简单的说就是master将数据库的改变写入二进制日志,slave同步这些二进制日志,并根据这些二进制日志进行数据操作。

环境介绍:

本次配置的环境为:centos7,使用LNMP1.4安装的Mysql5.5,主服务器为AWS服务器(下面简称主服务器),从服务器为京东云服务器(下面简称从服务器),现在需要将主服务器中名为img的数据库做主从同步,自动同步到从服务器上。

准备工具:xshell

一、复制数据库

1、首先要将主服务器的数据库复制到从服务器中。

用xshell连接主服务器,导出数据库:

mysqldump -uroot -p img >/root/img.sql
---输入数据库密码---

然后ls看下,可以看到一个 img.sql的文件。

2、用xshell连接从服务器,使用sftp命令远程到主服务器(有提示输入yes):

sftp root@主服务器IP地址
---输入ssh密码---
cd /root

然后ls看下,可以看到主服务器的目录,里面有个img.sql的文件,执行:

get img.sql

这样数据库文件就被复制到从服务器上了。 exit 退出sftp。

3、接下来在从服务器上建立一个名为img的数据库,排序规则设为uft8

mysql -uroot -p
---输入数据库密码---
create database img;
use img;
set names utf8;
source img.sql

这样就成功导入了,exit退出回到linux命令行。

二、主服务器配置

1、切回主服务器,用vim 编辑 /etc下的my.cnf,如图

未分类

这里主要配置的是server-id和log-bin。server-id用于区分不同主机上的数据库,log-bin是二进制文件的名字(可以随意命名)。同时,为了保证只同步需要的数据库,我们需要加上:

binlog-do-db=img
binlog-ignore-db=mysql

配置完成如图:

未分类

然后 :wq 保存退出,重启mysql:

service mysql restart

未分类

2、连接数据库进行如下操作:

mysql -uroot -p img
---输入数据库密码---
#以下为修改数据库连接权限
grant all privileges on *.* to 'root'@'%' identified by '自定义密码';
flush privileges;
#下面为显示bin-log文件名和位置
show master status;

执行完最后一条后我们可以看到:

未分类

记下红框处的文件名和数字。

三、从服务器配置

1、连接到从服务器,用vim修改/etc下的my.cnf文件,把server-id改为2。

重启mysql服务:

service mysql restart

2、连接数据库,配置从数据库:

mysql -uroot -p
---输入数据库密码---
stop slave;
change master to
master_user='root',
master_password='你设置的自定义密码',
master_host='主服务器地址',
master_log_file='mysql-bin.000011',   #记下的上图红框的文件名
master_log_pos=255;   #记下的上图红框的位置
start slave;
show slave statusG   #查看是否安装成功

如图,这两个是YES就成功了

未分类

四、其他说明

现在只要主服务器上的数据库有变动,从服务器上也会时时变动,达到了数据库异地容灾备份的功能。如果需要多服务器备份,那么可以把多个服务器中my.cnf中的server_id修改为不同,并按以上教程重新配置一遍,每个服务器的数据库可做主服务器也可以做从服务器。

如要同步多个数据库,可在主服务器的my.cnf 中,添加多条 binlog-do-db,指向多个数据库。

MongoDB客户端命令总结

一、常用命令

1、登录命令行(40008为自定义的端口)

mongo --port 40008

2、删除当前使用数据库

db.dropDatabase();

3、从指定主机上克隆数据库

db.cloneDatabase(“127.0.0.1”); 将指定机器上的数据库的数据克隆到当前数据库

4、从指定的机器上复制指定数据库数据到某个数据库

db.copyDatabase("mydb", "temp", "127.0.0.1");将本机的mydb的数据复制到temp数据库中

5、查看当前使用的数据库

db.getName();

6、显示当前db状态

db.stats();

7、当前db版本

db.version();

8、查看当前db的链接机器地址

db.getMongo();

9、显示当前所有用户

show users;

二、聚集集合查询

1、查询所有记录

db.userInfo.find();

相当于:select* from userInfo;

注:默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据。键入it命令不能带“;”,但是你可以设置每页显示数据的大小,用DBQuery.shellBatchSize= 50;这样每页就显示50条记录了。

2、查询去掉后的当前聚集集合中的某列的重复数据

db.userInfo.distinct("name");

会过滤掉name中的相同数据
相当于:select distict name from userInfo;

3、查询age = 22的记录

db.userInfo.find({"age": 22});

相当于: select * from userInfo where age = 22;

4、查询age > 22的记录

db.userInfo.find({age: {$gt: 22}});

相当于:select * from userInfo where age >22;

5、查询age < 22的记录

db.userInfo.find({age: {$lt: 22}});

相当于:select * from userInfo where age <22;

6、查询age >= 25的记录

db.userInfo.find({age: {$gte: 25}});

相当于:select * from userInfo where age >= 25;

7、查询age <= 25的记录

db.userInfo.find({age: {$lte: 25}});

8、查询age >= 23 并且 age <= 26

db.userInfo.find({age: {$gte: 23, $lte: 26}});

9、查询name中包含 mongo的数据

db.userInfo.find({name: /mongo/});
//相当于%%
select * from userInfo where name like ‘%mongo%';

10、查询name中以mongo开头的

db.userInfo.find({name: /^mongo/});
select * from userInfo where name like ‘mongo%';

11、查询指定列name、age数据

db.userInfo.find({}, {name: 1, age: 1});

相当于:select name, age from userInfo;

注:当然name也可以用true或false,当用ture的情况下河name:1效果一样,如果用false就是排除name,显示name以外的列信息。

12、查询指定列name、age数据, age > 25

db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1});

相当于:select name, age from userInfo where age >25;

13、按照年龄排序

升序:db.userInfo.find().sort({age: 1});
降序:db.userInfo.find().sort({age: -1});

14、查询name = zhangsan, age = 22的数据

db.userInfo.find({name: 'zhangsan', age: 22});

相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22′;

15、查询前5条数据

db.userInfo.find().limit(5);

相当于:select top 5 * from userInfo;

16、查询10条以后的数据

db.userInfo.find().skip(10);
相当于:select * from userInfo where id not in (
select top 10 * from userInfo);

17、查询在5-10之间的数据

db.userInfo.find().limit(10).skip(5);

注:可用于分页,limit是pageSize,skip是第几页*pageSize

18、or 查询

db.userInfo.find({$or: [{age: 22}, {age: 25}]});

相当于:select * from userInfo where age = 22 or age = 25;

19、查询第一条数据

db.userInfo.findOne();
相当于:selecttop 1 * from userInfo;
db.userInfo.find().limit(1);

20、查询某个结果集的记录条数

db.userInfo.find({age: {$gte: 25}}).count();

相当于:select count(*) from userInfo where age >= 20;

三、索引

1、创建索引

db.userInfo.ensureIndex({name: 1});
db.userInfo.ensureIndex({name: 1, ts: -1});

2、查询当前聚集集合所有索引

db.userInfo.getIndexes();

3、查看总索引记录大小

db.userInfo.totalIndexSize();

4、读取当前集合的所有index信息

db.users.reIndex();

5、删除指定索引

db.users.dropIndex("name_1");

6、删除所有索引索引

db.users.dropIndexes();

四、修改、添加、删除集合数据

1、添加

db.users.save({name: ‘zhangsan', age: 25, sex: true});

2、修改

db.users.update({age: 25}, {$set: {name: 'changeName'}}, false, true);
相当于:update users set name = ‘changeName' where age = 25;
db.users.update({name: 'Lisi'}, {$inc: {age: 50}}, false, true);
相当于:update users set age = age + 50 where name = ‘Lisi';
db.users.update({name: 'Lisi'}, {$inc: {age: 50}, $set: {name: 'hoho'}}, false, true);
相当于:update users set age = age + 50, name = ‘hoho' where name = ‘Lisi';

3、删除一条数据

db.users.remove({age: 132});

4、清空表数据

db.users.drop();

五、其他

1、循环添加数据

> for (var i = 0; i < 30; i++) {
... db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});
... };

这样就循环添加了30条数据,同样也可以省略括号的写法

> for (var i = 0; i < 30; i++) db.users.save({name: "u_" + i, age: 22 + i, sex: i % 2});

2、查询之前的错误信息

db.getPrevError();

3、清除错误记录

db.resetError();

Nginx+Tomcat+memcached高可用会话保持

一、概述

之前文章已经描述了企业高可用负载相关的架构及实现,其中常用的nginx或haproxy,LVS结合keepalived做前端高可用调度器;但之前没有提到会话高可用保持;
本文通过 Tomcat Session Replication Cluster(tomcat自带)和tomcat结合memcat及第三方组件实现Tomcat Memcache Session Server高可用会话缓存服务;
实现的效果:
同一客户端访问业务网站,经过调度器负载调度到达后端,不管选择的是那个后端,session ID都不变,都保存在两台或多台的memcached缓存中(负载冗余);以保持持会话;

架构图:

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;tomcat 把会话通过组播的方式复制到集群各节点;所有节点共享会话;

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;并把session存储到两台memcached中;客户端刷新(不换浏览器)时,请求换成另一个后端服务器响应时session ID保持不变;

测试环境:

nginx: CentOS7 epel 安装nginx WAN:172.16.3.152 LAN:192.168.10.254
tomcat A: CentOS7 node1.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached(现实环境中单独服务器)
tomcat B: CentOS7 nodde2.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached 现实环境中单独服务器)
测试客户端ubuntu 16.04

cat /etc/hosts
172.16.3.152       www.san.com

二、安装配置集群

nginx安装

[root@nginx ~]# yum install epel-release -y
[root@nginx ~]# yum install nginx -y

nginx配置

在/etc/nginx/nginx.conf http段添加如下行

    upstream tcsrvs {
        server 192.168.10.11:8080;
        server 192.168.10.12:8080;
        }

cat /etc/nginx/conf.d/san.com.conf

[root@nginx ~]# cat /etc/nginx/conf.d/san.com.conf 
server {
            listen 80;
            server_name www.san.com;
            location / {
                proxy_pass http://tcsrvs;
                }
        }

Tomcat配置:
两台均需要安装

#yum install epel-release -y
#yum install java-1.8.0 java-1.8.0-openjdk-devel tomcat tomcat-webapps tomcat-admin-webapps tomcat-docs-webapp  -y

说明:也可以通过oracle官方下载jdk 下载tomcat 解压到指定目录并添加环境变量;一般企业推荐此种方式;为了快捷,我们用epel仓库中的稳定版本;

添加测试页

yum 安装的tomcat工作目录在/var/lib/tomcat/webapps 分别在node1与node2上,此目录下创建测试项目,title 分别叫Tomcat A与Tomcat B 颜色分别为green与red;以示区别;生产环境node1 与node2内容一致;这里为了测试区别node1与node2内容;

#mkdir -pv /var/lib/tomcat/webapps/test/{WEB-INF,META-INF,classes,lib}
#cat /var/lib/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
 <html>
      <head><title>Tomcat A</title></head>
          <body>
             <h1><font color="red">TomcatA.san.com</font></h1>
           <table align="centre" border="1">
            <tr>
                <td>Session ID</td>
            <% session.setAttribute("san.com","san.com"); %>
            <td><%= session.getId() %></td>
            </tr>
            <tr>
            <td>Created on</td>
           <td><%= session.getCreationTime() %></td>
           </tr>
      </table>
    </body>
</html>

配置管理页密码

tomcat与管理程序安装好后配置访问密码
修改注释/etc/tomcat/tomcat-users.xml文件

<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>

备份默认/etc/tomcat/server.xml文件

cd /etc/tomcat
cp server.xml server.xml_def

测试页访问

http://www.san.com/test 如图:出现Tomcat A

未分类

Ctrl+F5强制刷新 又出现Tomcat B

未分类

引发问题:如果是两台内容一样的配置,客户端访问刷新一下就换到另一个后端处理;类似通过session保留信息的服务(购买物车)如何保留?换句话说,如何保持会话不中断,无论请求被分配到那一个后端?

解决方案

1)会话sticky(粘性):分为source_ip 基于源ip和cookie

source_ip在不同的调度器上有不同的实现方式:
lvs:sh算法;
nginx:ip_hash或hash $request_uri consistent(一致性哈希算法)
haproxy: source

cookie:
nginx:hash 或 hash $cookie_name consistent;
haproxy:cookie

2)会话集群(session cluster):delta session manager

3)session server: redis(store),memcached(cache)

以下基于tomcat自带会话集群与memcached实现会话保持 功能;

三、Tomcat Session Replication Cluster配置

Tomcat Session Replication Cluster中文又叫 tomcat 会话复制集群,即会话通过组播方式复制到每一个后端tomcat节点;
可参考自带帮助文档:http://www.san.com/docs/cluster-howto.html
两台node1 node2节点/etc/hosts中添加如下:

#cat /etc/hosts
192.168.10.11 node1.san.com node1
192.168.10.12 node2.san.com node2

两台tomcat 节点sever.xml的Host字段中添加如下内容:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.10.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"           <!--   如果没有/etc/hosts解析则需要本机ip   -->
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

复制 /etc/tomcat/web.xml /var/lib/tomcat/webapps/test/WEB-INF/ 下并在web.xml的”“字段下添加 ““;
重启tomcat 并再次访问http://www.san.com/test 如图:

未分类

Ctrl + F5强制刷新如图:

未分类

可以可出会话得到保持,只要是从同一个客户端中请求,刷新或关闭重新打开(基于同一个浏览器) 只要会话没有过期,会话(session id) 无论来自那个后端,均是一样;

缺点:

tomcat自带支持会话集群(能过多播方式发送各节点);但有一个缺点;后端tomcat节点过多时效率低下,不适用大规模;

四、Tomcat Memcache Session Server高可用配置

原理说明:
客户端请求到达前端nginx调度器并分配到后端某tomcat节点时,tomcat会优先使用本机内存保存session,当一个请求结束后,tomcat会通过第三方组件(kryo,javolution,xstream,flexjson)把session序列化并发送到memcached节点上存放作备份,第二次请求时,如果本地有session就直接返回,第二次请求结束,把session修改后的信息更新到后端的memcached服务器,以这样的方式来保持本地的session与memcached上的session同步。当这个tomcat节点宕机时,那么用户的下一次请求就会被前端的负载均衡器路由到另一个tomcat节点上,而这个节点上并没有这个用户的session信息,这个节点就从memcached服务器上去读取session,并把session保存到本地的内存,当请求结束,session又被修改,再送回到memcached进行存放备份
当后端配置了多台memcached时,tomcat在更新session信息时会同时向多个memcached节点更新session,当一个memcached节点故障时,tomcat可以从选择一个正常工作的memcached节点读取session信息来发送给用户的浏览器,让其重置session信息,这样,memcached也达到了高可用的目的;
以下操作均在两台node上操作

还原默认配置文件

#cd /etc/tomcat/
#cp server.xml server.xml_cluster
#cp server.xml_def server.xml
#systemctl stop tomcat

安装memcached服务

#yum install memcached -y
#systemctl start memcached

memcache配置(默认即可,生产环境时需要加大内存与并发连接数)

# cat /etc/sysconfig/memcached 
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

两台/etc/tomcat/server.xml Host段中添加如下内容:

<Context path="/test" docBase="test" reloadable="true">
        <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
                memcachedNodes="m1:192.168.10.11:11211,m2:192.168.10.12:11211"
                failoverNodes="m1"
                requestUriIgnorePattern=".*.(ico|png|gif|jpg|css|js)$"
                transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"/>
        </Context>

说明:

添加两个冗余备份memcached节点分别叫m1,m2 failoverNodes=”m1″ 表示m1作为备份;当m2失败时连接;即使用m2;

安装对应版本组件

下载以下JAR包到tomcat库目录;
cd /usr/share/tomcat/lib 
wget http://www.java2s.com/Code/JarDownload/javolution/javolution-5.5.1.jar.zip  
#需要解压 unzip javolution-5.5.1.jar.zip
wget http://repo1.maven.org/maven2/net/spy/spymemcached/2.12.1/spymemcached-2.12.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/msm-javolution-serializer/2.1.1/msm-javolution-serializer-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager-tc7/2.1.1/memcached-session-manager-tc7-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager/2.1.1/memcached-session-manager-2.1.1.jar

注意:epel安装的tomcat 和openjdk版本如下:
openjdk: “1.8.0_161”
tomcat : “7.0.76”
以上第三方插件须和对应的版本是兼容的;如发现tomcat启动有问题;无法访问或如下类似错误

#tail -fn 100 /var/log/tomcat/catalina.xxxx.log
三月 23, 2018 4:12:52 下午 org.apache.catalina.core.StandardContext startInternal
严重: The session manager failed to start
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[/test]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5643)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)

则表示第三方组件与tomcat不兼容!请重新下载版本;

测试:
浏览器访问http://www.san.com/test 如图:

未分类

Ctrl+F5强刷新 如图:

未分类

从测试上可以看出目前已经通过memcache存储session等缓存信息;并同步到两台memcache上;当前只使用m2节点;

总结

通过nginx快速实现负载tomcat应用;引用session不可保持问题;通过自带的Tomcat Session Replication Cluster和结合memcached及第三方组件实现高可用会话缓存服务来保持会话;前者不适合大规模应用;

debian9安装mariadb apache php7

一、

apt install apache2

二、

apt install php (安装后测试phpinfo)
<?php
phpinfo();
?>

三、

apt install mariadb-server

1、安装完 MariaDB 之后,运行 mysql_secure_installation 可以设置root密码及删除测试账户之类的信息。

2、新建一个用户:

insert into mysql.user(Host,User,Password) values("localhost","admin",password("admin"));

刷新系统权限表

flush privileges;

这样就创建了一个名为:admin 密码为:admin 的用户

3、创建数据库(在root权限下)

create database abc;
//授权admin用户拥有abc数据库的所有权限。

grant all privileges on abc.* to admin@localhost identified by 'admin';
//刷新系统权限表
mysql>flush privileges;

4、删除用户。

@>mysql -u root -p
@>密码
mysql>

DELETE FROM mysql.user WHERE User="admin" and Host="localhost";
mysql>flush privileges;

5、mysql>drop database abc;//删除用户的数据库

6、查看数据库中具体某个用户的权限

mysql> show grants for 'cactiuser'@'%';

7、查询所有用户:

SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user;

8、显示所有数据库

show databases;

9、修改指定用户密码。

@>mysql -u root -p
@>密码
mysql>

update mysql.user set password=password('新密码') where User="admin" and Host="localhost";
mysql>flush privileges;

10、删除某个用户权限

revoke all privileges on wordpress.* from 'admin'@'localhost';

MariaDB/MySQL中的变量

在MySQL/MariaDB中有好几种变量类型:用户自定义变量、系统变量、一般的临时变量(即本地变量,或称为局部变量)。

1. 用户变量

用户变量是基于会话的,也是基于用户的,所以我觉得称之为会话变量更合适,但会话变量一般用来表示系统会话变量(后面会说明),所以还是称之为用户变量好了。

只有本用户才能引用自身的用户变量,其他用户无法引用,且当用户退出会话时,用户变量自动销毁。

用户变量使用”@”开头,用户变量可以直接赋值,无需事先声明。在引用未赋值的用户变量时,该变量值为null。

有以下三种方式设置用户变量:

  • set语句,此时可以使用”=”或者”:=”操作符;
  • select语句,此时只能使用”:=”格式赋值,因为除了set语句中,”=”都会被视为比较操作符。;
  • select … into var_name from TABLE语句,此时要求select语句只能返回标量值,即单行数据。因此为了保险,select into var_name的时候,应尽量结合limit语句限制输出。
set @a1=1,@a2=3,@a3:=2;
select @a4:=@a1+@a3;
select 33,'abc' into @a5,@a6 from dual;

查看变量值可以使用select语句。

MariaDB [test]> select @a1,@a2,@a3,@a4,@a5,@a6,@a7;
+------+------+------+------+------+------+------+
| @a1  | @a2  | @a3  | @a4  | @a5  | @a6  | @a7  |
+------+------+------+------+------+------+------+
|    1 |    2 |    3 |    4 |    5 | abc  | NULL |
+------+------+------+------+------+------+------+

在mariadb 10.2.6中,引入了一张系统架构表information_schema.USER_VARIABLES,该表中记录了当前用户当前会话定义的用户变量信息。该信息架构表在mysql中没有。

MariaDB [test]> SELECT * FROM information_schema.USER_VARIABLES;           
+---------------+----------------+---------------+--------------------+
| VARIABLE_NAME | VARIABLE_VALUE | VARIABLE_TYPE | CHARACTER_SET_NAME |
+---------------+----------------+---------------+--------------------+
| a6            | abc            | VARCHAR       | utf8               |
| i             | 2              | INT           | utf8               |
| a5            | 33             | INT           | utf8               |
| a1            | 1              | INT           | utf8               |
| a4            | 3              | INT           | utf8               |
| a2            | 3              | INT           | utf8               |
| a3            | 2              | INT           | utf8               |
+---------------+----------------+---------------+--------------------+

2. 系统变量

在MySQL/mariadb中维护两种系统变量:全局系统变量和会话系统变量。系统变量是用来设置MySQL服务运行属性和状态的。

全局系统变量使用global或者”@@global.”关键字来设置。会话系统变量使用session或者”@@session.”关键字来设置,其中session可以替换为Local,它们是同义词。如果省略这些关键字,则默认为session系统变量。设置global系统变量要求具有super权限。

-- 设置全局系统变量
set global sort_buffer_size=32M;
set @@global.sort_buffer_size=32M;
-- 设置会话系统变量
set session sort_buffer_size=32M;
set @@session.sort_buffer_size=32M;
set sort_buffer_size=32M;
-- 查看全局系统变量值
select @@global.sort_buffer_size;
show global variables like "sort_buffer%";
-- 查看会话系统变量,不能使用select sort_buffer_size
select @@session.sort_buffer_size;
select @@sort_buffer_size;
show [session] variables like "sort_buffer%";
-- 一次性设置多个变量,包括会话变量、全局变量以及用户变量
SET @x = 1, SESSION sql_mode = '';
SET GLOBAL sort_buffer_size = 1000000, SESSION sort_buffer_size = 1000000;
SET @@global.sort_buffer_size = 1000000, @@local.sort_buffer_size = 1000000;
SET GLOBAL max_connections = 1000, sort_buffer_size = 1000000;

全局系统变量对全局有效,当有新的会话打开时,新会话会继承全局系统变量的值,所以设置全局系统变量之后新打开的会话都会继承设置后的值。设置全局系统变量对已经打开的连接无效,但是其他已经打开的连接可以查看到设置后的全局系统变量值。

系统变量按照是否允许在运行时修改,还分为动态变量和静态变量。能在运行过程中修改的变量称为动态变量,只能在数据库实例关闭状态下修改的变量称为静态变量或只读变量。动态变量使用set修改。如果在数据库实例运行状态下修改静态变量,则会给出错误。如:

set @@innodb_undo_tablespaces=3;
ERROR 1238 (HY000): Variable 'innodb_undo_tablespaces' is a read only variable

系统变量除了可以在运行中的环境下设置,还可以在配置文件中或者mysqld/mysqld_safe这样的命令行中设置,甚至mysql客户端命令行也可以传递。在配置文件中设置系统变量时,下划线或者短横线都允许,它们表示同一个意思。例如下面的两行配置是等价的:

innodb_file_per_table=1
innodb-file-per-table=1

3. 局部变量

局部变量也称为本地变量,只能在begin…and语句块中生效。它不像用户变量,本地变量必须使用declare事先声明,所以declare也必须在begin…end中使用。

局部变量无论是声明还是调用的时候都不需要任何多余的符号(即不需要@符号),直接使用其名称var_name即可。

使用declare声明变量,可以一次性声明多个同类型的变量,需要时可有直接为其指定默认值,不指定时默认为null。

decalre var_name,... type [default value];

使用set为变量赋值。MySQL/mariadb中set支持一次性赋值多个变量。

在begin…end中的set是一般set语句的扩展版本,它既可以设置系统变量、用户变量,也可以设置此处的本地变量。

set var_name=expr,[var_name=expr1,...]

或者使用select…into语句从表中获取值来赋值给变量,但是这样的赋值行为要求表的返回结果必须是单列且单行的标量结果。例如下面的语句将col的列值赋值给var_name变量。

select col into var_name from table_name;

因为局部变量只能在begin…end中使用,所以此处使用存储过程的例子来演示。

DROP PROCEDURE IF EXISTS haha;
DELIMITER $$
CREATE PROCEDURE haha()
BEGIN
    DECLARE a INT;
    SET a=1;
    SET @i:=2;
    SELECT a,@i;
END$$
DELIMITER ;

CALL haha();
     a        @i
------  --------
     1         2

在MySQL中,begin…end只能定义在存储程序中,所以declare也只能定义在存储程序内。但在mariadb中,begin…end是允许定义在存储程序(存储函数,存储过程,触发器,事件)之外的,所以decalre也算是能够定义在存储程序之外吧。需要定义在存储程序之外时,使用 begin not atomic 关键字即可。例如:

delimiter $$ 
begin not atomic
    declare a int;
    set a=3;
    select a;
end$$

3.1 declare锚定其他对象的数据类型

在mariadb 10.3中(注意版本号,目前10.3版本还在测试中),declare语句允许在存储程序中使用TYPE OF和ROW TYPE OF 关键字基于表或游标来锚定数据类型。在mysql中不支持数据类型的锚定功能。

例如:

DECLARE tmp TYPE OF t1.a; -- 基于表t1中的a列获取数据类型
DECLARE rec1 ROW TYPE OF t1; -- 锚定表t1中行数据类型
DECLARE rec2 ROW TYPE OF cur1; -- 基于游标cur1获取行数据类型

通过其他对象来锚定本地变量的数据类型时,如果对象的数据类型改变,则本地数据类型也随之改变。这在某些时候非常有利于维护存储程序。

在定义存储程序时,不会检查declare锚定的对象是否存在。但在调用存储程序时,会先检查锚定对象是否存在。

当declare语句的锚定是基于表对象(不是游标)时,在调用存储程序的瞬间就会检查锚定的表是否存在,然后立刻声明该变量。因此:

  • (1).带有锚定功能的decalre语句可以定义在存储程序的任意位置;
  • (2).在存储程序中删除锚定的表对象,或者修改了锚定的表结构,都不会改变存储程序调用时声明的变量类型;
  • (3).所有带锚定功能的declare都是在存储程序调用之初被赋值的。

当declare语句的锚定是基于游标对象时,变量的数据类型是在执行变量声明语句时才获取到的。数据类型仅只锚定一次,之后不再改变。如果游标中的ROW TYPE OF变量是定义在一个循环之中,则数据类型在循环的开头就已经获取,且之后的循环不再改变。

示例:

create table t1(a int,b char(20));
drop procedure if exists haha;
delimiter $$
create procedure haha()
begin
    declare x type of t1.a;
    set x=1;
    select x;
end$$
delimiter ;

call haha();

修改系统及nginx程序最大访问数,解决高并发问题

修改最大访问数。先更改内核参数

查看:

ulimit -a

未分类

查看此行

open files (-n) 140000

进入系统配置文件进行修改:

[root@bogon ~]#vi /etc/systemd/system.conf
#DefaultLimitNOFILE=
DefaultLimitNOFILE=140000
#DefaultLimitAS=
#DefaultLimitNPROC=
DefaultLimitNPROC=140000

重启生效

但是查看程序发现Max open files依然还是没变

过滤出程序端口号: ps aux |grep nginx
查看linmits信息: cat /proc13636/limits // Max processes ;Max open files` 主要查看这两个数据变化

未分类

ps:上面的图时已经修改过的。默认是1024
所以要再进行一次设置才行

vim /usr/lib/systemd/system/nginx.service //需要在此设置nginx访问数大小

未分类

在[Unit]里面添加 LimitNOFILE=10000000 //数字随意但是不能超过系统值

systemctl restart nginx //重启 ,会报警让你开启 systemctl daemon-reload,那你就开启喽~~~

systemctl daemon-reload //敲这个命令

ps aux |grep nginx // 再次过滤uuid

cat /proc/14524/limits //查看数值发生改变

问题解决。

使用nginx后如何在web应用中获取用户ip及原理解释

问题背景

在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用request.getRemoteAddr()就可以获取到客户端ip,但是当我们使用了nginx作为反向代理后,使用request.getRemoteAddr()获取到的就一直是nginx服务器的ip的地址,那这时应该怎么办?

part1:解决方案

我在查阅资料时,有一本名叫《实战nginx》的书,作者张晏,这本书上有这么一段话“经过反向代理后,由于在客户端和web服务器之间增加了中间层,因此web服务器无法直接拿到客户端的ip,通过$remote_addr变量拿到的将是反向代理服务器的ip地址”。这句话的意思是说,当你使用了nginx反向服务器后,在web端使用request.getRemoteAddr()(本质上就是获取$remote_addr),取得的是nginx的地址,即$remote_addr变量中封装的是nginx的地址,当然是没法获得用户的真实ip的,但是,nginx是可以获得用户的真实ip的,也就是说nginx使用$remote_addr变量时获得的是用户的真实ip,如果我们想要在web端获得用户的真实ip,就必须在nginx这里作一个赋值操作,如下:
proxy_set_header X-real-ip $remote_addr;
其中这个X-real-ip是一个自定义的变量名,名字可以随意取,这样做完之后,用户的真实ip就被放在X-real-ip这个变量里了,然后,在web端可以这样获取:
request.getAttribute("X-real-ip")
这样就明白了吧。

part2:原理介绍

这里我们将nginx里的相关变量解释一下,通常我们会看到有这样一些配置

server {
        listen       88;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location /{
            root   html;
            index  index.html index.htm;
                            proxy_pass                  http://backend; 
           proxy_redirect              off;
           proxy_set_header            Host $host;
           proxy_set_header            X-real-ip $remote_addr;
           proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
                     # proxy_set_header            X-Forwarded-For $http_x_forwarded_for;
        }

我们来一条条的看

1. proxy_set_header X-real-ip $remote_addr;
这句话之前已经解释过,有了这句就可以在web服务器端获得用户的真实ip
但是,实际上要获得用户的真实ip,不是只有这一个方法,下面我们继续看。

2. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
我们先看看这里有个X-Forwarded-For变量,这是一个squid开发的,用于识别通过HTTP代理或负载平衡器原始IP一个连接到Web服务器的客户机地址的非rfc标准,如果有做X-Forwarded-For设置的话,每次经过proxy转发都会有记录,格式就是client1, proxy1, proxy2,以逗号隔开各个地址,由于他是非rfc标准,所以默认是没有的,需要强制添加,在默认情况下经过proxy转发的请求,在后端看来远程地址都是proxy端的ip 。也就是说在默认情况下我们使用request.getAttribute(“X-Forwarded-For”)获取不到用户的ip,如果我们想要通过这个变量获得用户的ip,我们需要自己在nginx添加如下配置:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
意思是增加一个$proxy_add_x_forwarded_for到X-Forwarded-For里去,注意是增加,而不是覆盖,当然由于默认的X-Forwarded-For值是空的,所以我们总感觉X-Forwarded-For的值就等于$proxy_add_x_forwarded_for的值,实际上当你搭建两台nginx在不同的ip上,并且都使用了这段配置,那你会发现在web服务器端通过request.getAttribute(“X-Forwarded-For”)获得的将会是客户端ip和第一台nginx的ip。

那么$proxy_add_x_forwarded_for又是什么?
$proxy_add_x_forwarded_for变量包含客户端请求头中的”X-Forwarded-For”,与$remote_addr两部分,他们之间用逗号分开。
举个例子,有一个web应用,在它之前通过了两个nginx转发,即用户访问该web通过两台nginx。
在第一台nginx中,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量的”X-Forwarded-For”部分是空的,所以只有$remote_addr,而$remote_addr的值是用户的ip,于是赋值以后,X-Forwarded-For变量的值就是用户的真实的ip地址了。

到了第二台nginx,使用
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
现在的$proxy_add_x_forwarded_for变量,X-Forwarded-For部分包含的是用户的真实ip,$remote_addr部分的值是上一台nginx的ip地址,于是通过这个赋值以后现在的X-Forwarded-For的值就变成了“用户的真实ip,第一台nginx的ip”,这样就清楚了吧。

最后我们看到还有一个$http_x_forwarded_for变量,这个变量就是X-Forwarded-For,由于之前我们说了,默认的这个X-Forwarded-For是为空的,所以当我们直接使用proxy_set_header X-Forwarded-For $http_x_forwarded_for时会发现,web服务器端使用request.getAttribute(“X-Forwarded-For”)获得的值是null。如果想要通过request.getAttribute(“X-Forwarded-For”)获得用户ip,就必须先使用proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;这样就可以获得用户真实ip。

ps:变量名太长,自己感觉看着好晕,打字打的我眼睛都花了,希望解释清楚了,如果有疑问可以和我交流,共同学习。

使用Kibana分析Nginx访问日志

介绍

使用Kibana可以很方便的分析Nginx访问日志,可以分析出如:网站的访问趋势、页面访问次数、访问者地区分布、访问者所使用的浏览器设备占比等,可以发挥你的想象任意指定kibana的图表。

效果图:

未分类

未分类

未分类

未分类

具体用法

以创建浏览器统计饼图为例:

1、进入Visualize栏目,选择Pie图

未分类

2、下一步选择Index

未分类

3、配置数据

配置如下图,最后点击左上角的保存按钮,一张浏览器统计饼图就做好了。

未分类

最后可以把做好的图标添加到仪表盘Dashboard来方便查看。

附上一些配置

未分类

未分类

未分类

未分类

未分类

未分类