1、MySql锁机制

锁机制的作用:

解决因为资源共享,而造成的并发问题。

没有锁机制时:

例如一号用户和二号用户都要去买同一件商品(假如这件商品是一件衣服),一号用户手速稍微快了一些,于是就先买到了这件衣服,但是因为没有“锁机制”,于是就造成了二号用户不知道这件衣服已经被人买了,所以就造成了“并发问题”。

有锁机制时:

但是有了“锁机制”,一号用户在买了衣服之后就会“对衣服进行加锁”,二号用户看到“衣服被加锁了”,于是就去“等待着衣服被解锁”。

在“衣服未被解锁”的过程中,衣服则会被一号用户进行“试穿”、“下单”、“付款”、“打包”、或者“不满意,取消订单”一系列的操作,这一系列操作之后“衣服会被解锁”。

如果一号用户这边觉得衣服还行打算买下来,在买完之后“衣服就会被解锁”,二号用户这边看到“衣服被解锁了”就会去查看衣服是否还存在,很明显已经不存在了,因为一号用户已经把衣服买了,如果一号用户“不满意,取消订单”,那么二号用户这边就可以购买这件衣服。

因此通过锁机制就很好的解决了买衣服造成的并发问题。

2、MySql锁的分类

按照“操作类型”分:

1、读锁(也叫“共享锁”):

对同一个数据(衣服),多个读操作可以同时进行,互不干扰。

2、写锁(也叫“互斥锁”)

如果当前写操作没有完毕(买衣服的一系列操作),则无法进行其他的读操作、写操作。

按照“操作范围”分:

1、表锁 :

一次性对一张表整体加锁。如MyISAM存储引擎使用表锁,开销小、加锁快;无死锁;但锁的范围大,容易发生锁冲突、并发度低。

2、行锁(与表锁的特性完全相反):

一次性对一条数据加锁。如InnoDB存储引擎使用行锁,开销大,加锁慢;容易出现死锁;锁的范围较小,不易发生锁冲突,并发度高(很小概率发生高并发问题:脏读、幻读、不可重复度、丢失更新等问题)。

3、页锁(几乎遇不见)

页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。

3、MySql表锁示例

准备工作:

1、创建两张表,表table和表lock

create table tab(
id int primary key auto_increment , 
name varchar(20)
)engine myisam;
insert into tab(name) values('tab1');
insert into tab(name) values('tab2');
insert into tab(name) values('tab3');
insert into tab(name) values('tab4');
insert into tab(name) values('tab5');
create table locktab(
id int primary key auto_increment , 
name varchar(20)
)engine myisam;
insert into locktab(name) values('locktab1');
insert into locktab(name) values('locktab2');
insert into locktab(name) values('locktab3');
insert into locktab(name) values('locktab4');
insert into locktab(name) values('locktab5');

2、再新建两个会话

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

加锁相关命令:

1、增加锁:

lock table 表1 read/write,表2 read/write,...;

2、释放锁:

unlock tables;

3、查看加锁的表:

show open tables;

加读锁(也叫“共享锁”):

会话192.168.11.101(1):

给表locktab加读锁:

lock table locktab read;
select * from locktab; --执行读操作(查),可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

delete from locktab where id =1; --执行写操作(增删改),不可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

操作没有加锁的表tab:

select * from tab; --读操作,不可以
delete from tab where id = 1; --写操作,不可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

结论:

1、如果某一个会话对A表加了read锁,则该会话可以对A表进行读操作、不能进行写操作;且该会话不能对其他表进行读、写操作。

2、即如果给A表加了读锁,则当前会话只能对A表进行读操作。

会话192.168.11.101(2),也就是其它会话:

操作加锁了的表locktab:

select * from locktab; --读操作(查),可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

delete from locktab where id =1; --写操作,会“等待”会话192.168.11.101(1)将锁释放

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

操作没有加锁的表tab:

select * from tab;  --读操作(查),可以
delete from tab where id = 1; --写操作(增删改),可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

再来在“会话192.168.11.101(1)”中把锁释放了,看看“delete from locktab where id =1;”是否会释放阻塞:

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

总结:

“会话192.168.11.101(1)”给表locktab加了锁,其他会话的操作:

1、可以对其他表(表locktab以外的表)进行读、写操作。

2、对表locktab:读操作可以,写操作需要等待释放锁。

加写锁(也叫“互斥锁”):

会话192.168.11.101(1):

给表locktab加读锁:

lock table locktab write;
select * from locktab; --读操作,可以
delete from locktab where id = 5; --写操作,可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

操作没有加锁的表tab:

select * from tab; --读操作,不可以
delete from tab where id = 3; --写操作,不可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

总结:

当前会话(会话192.168.11.101(1))可以对加了写锁的表进行任何操作(增删改查),但是不能操作(增删改查)其他表。

会话192.168.11.101(2):

操作加锁了的表locktab:

select * from locktab; --写操作,会阻塞

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

delete from locktab where id = 2; --写操作,也会阻塞

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

对“会话192.168.11.101(2)”中加写锁的表可以进行增删改查的前提是:等待“会话192.168.11.101(1)”释放写锁。

操作没加锁的表tab:

select * from tab; --读操作,可以
delete from tab where id = 2; --写操作,可以

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

MySQL表级锁的锁模式:

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(DML)前,会自动给涉及的表加写锁。

所以对MyISAM表进行操作,会有以下情况:

1、对MyISAM表的读操作(加读锁),不会阻塞其他进程(会话)对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。

2、对MyISAM表的写操作(加写锁),会阻塞其他进程(会话)对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。

分析表锁定:

查看哪些表加了锁:

show open tables

1代表被加了锁

分析表锁定的严重程度:

show status like 'table%';

Table_locks_immediate:即可能获取到的锁数。

Table_locks_waited:需要等待的表锁数(如果该值越大,说明存在越大的锁竞争)。

一般建议:

Table_locks_immediate/Table_locks_waited > 5000, 建议采用InnoDB引擎,否则MyISAM引擎。

4、MySql行锁示例

准备工作:

unlock tables; --需要先释放之前加的表锁
create table linelock(
id int(5) primary key auto_increment,
name varchar(20)
)engine=innodb ;
insert into linelock(name) values('1');
insert into linelock(name) values('2');
insert into linelock(name) values('3');
insert into linelock(name) values('4');
insert into linelock(name) values('5');

为了研究行锁,暂时将自动commit关闭,“set autocommit = 0;”,以后需要通过“commit”关键字提交写操作:

set autocommit = 0;

mysql默认自动commit,oracle默认不会自动commit。

行锁,操作同一条数据:

会话192.168.11.101(1):写操作

insert into linelock values(6,'a6');

会话192.168.11.101(2):写操作,同样的数据

update linelock set name='ax' where id = 6;

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

总结:

1、如果会话x对某条数据a进行DML操作(研究时:关闭了自动commit的情况下),则其他会话必须等待会话x结束事务(commit/rollback)后,才能对数据a进行操作。

2、表锁是通过unlock tables,也可以通过事务解锁;行锁是通过事务解锁。

行锁,操作不同数据:

会话192.168.11.101(1): 写操作

insert into linelock values(8,'a8');

会话192.168.11.101(2): 写操作,不同的数据

update linelock set name='ax' where id = 5;

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

总结:

行锁,一次锁一行数据;因此如果操作的是不同数据,则不干扰。

行锁的注意事项:

1、如果没有索引,则行锁会转为表锁

先给字段“name”加个索引:

show index from linelock;

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

可以看到有一条索引,这是主键索引,建表创建主键的时候自动加的。

这是mysql的基础,无论是创建主键还是外键都会自动创建主键索引或外键索引。

给name加个索引:

alter table linelock add index idx_linelock_name(name);

会话192.168.11.101(1): 写操作

update linelock set name = 'ai' where name = '3';

会话192.168.11.101(2): 写操作,不同的数据

update linelock set name = 'aiX' where name = '4';

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

可以看到在索引没有失效的情况下所有会话都可以对数据进行写操作。

会话192.168.11.101(1): 写操作

update linelock set name = 'ai' where name = 3;

会话192.168.11.101(2): 写操作,不同的数据

update linelock set name = 'aiX' where name = 4;

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

可以发现,数据被阻塞了(加锁)。

原因:如果索引列发生了类型转换,则索引失效。 因此此次操作,会从行锁转为表锁。

2、行锁的一种特殊情况:间隙锁:值在范围内,但却不存在。

此时linelock表中 没有id=7的数据:

MySql锁机制(全网最全、最详细、最清晰)-编程知识网

“update linelock set name ='x' where id >1 and id<9;”即在此where范围中,没有id=7的数据,则id=7的数据成为间隙。

间隙:Mysql会自动给间隙加锁(间隙锁)。即会自动给id=7的数据加间隙锁(行锁)。

行锁:如果有where,则实际加锁的范围就是where后面的范围(不是实际的值)。

如果仅仅是查询数据,能否加锁? 可以使用for update:

select * from linelock where id =2 for update;

通过for update对query语句进行加锁。

mysql中,以下三种方式都可以将自动提交关闭:

set autocommit =0;
start transaction;
begin;

行锁:

InnoDB默认采用行锁。

缺点:

比表锁性能损耗大。

优点:

并发能力强,效率高。

因此建议,高并发用InnoDB,否则用MyISAM。

行锁分析:

show status like '%innodb_row_lock%';

Innodb_row_lock_current_waits:当前正在等待锁的数量。

Innodb_row_lock_time:等待总时长。从系统启动到现在一共等待的时间。

Innodb_row_lock_time_avg:平均等待时长。从系统启动到现在平均等待的时间。

Innodb_row_lock_time_max:最大等待时长。从系统启动到现在最大一次等待的时间。

Innodb_row_lock_waits:等待次数。从系统启动到现在一共等待的次数。