锁是计算机协调多个进程或者线程并发访问某一资源的机制,在数据库中,除传统的计算资源(如CPU、RAM、I/O等)争用以外,数据也是一种供许多用户共享的资源,如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,从这个角度来说,锁对数据库而言显得尤其重要。

2、锁的分类

①、从对数据的操作(读/写)分

读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。

写锁(排他锁):当前写没有操作完成前,它会阻断其他写锁和读锁;

②、从对数据操作的粒度分

表锁:对整个表的锁;

行锁:值锁住某一行数据。

2.1、表锁

偏向MyISAM引擎,开销小,加锁快;锁粒度大,发生锁冲突的概率最高,并发度最低。

2.1.1、案例分析

建表语句,指定MyISAM引擎:1


9create table mylock(
id int not null primary key auto_increment,
name varchar(20)
) engine myisam;
create table book(
id int not null primary key auto_increment,
name varchar(20)
) engine myisam;

插入数据:1


11insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
insert into book(name) values('a2');
insert into book(name) values('b2');
insert into book(name) values('c2');
insert into book(name) values('d2');
insert into book(name) values('e2');

手动添加表锁:1lock table read(wirte)

解锁:1unlock tables

①、加读锁

mysql 某条数据被锁 mysql 数据库锁_读锁

mysql 某条数据被锁 mysql 数据库锁_键值_02

mysql 某条数据被锁 mysql 数据库锁_键值_03

②、加写锁

mysql 某条数据被锁 mysql 数据库锁_键值_04

mysql 某条数据被锁 mysql 数据库锁_读锁_05

2.1.2、案例结论

MyISAM在执行查询语句(select)前,会自动给涉及的所以表加读锁,在执行增删改操作前,会自动给涉及的表加写锁,MySQL的表级锁有两种模式:

①、表共享读锁(Table Read Lock)

②、表独占写锁(Table Write Lock)

mysql 某条数据被锁 mysql 数据库锁_MySQL数据库锁构建_06

结论:

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

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

简而言之:读锁会阻塞写,但不会阻塞读,而写锁则会把读和写都阻塞

2.2.3、表锁分析

①、查看哪些表被锁了,使用如下命令:1show open tables

mysql 某条数据被锁 mysql 数据库锁_键值_07

其中In_use列为1,表锁该表被锁。

②、如何分析表锁定

可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定1show status like 'table%'

mysql 某条数据被锁 mysql 数据库锁_键值_08

这里有有两个状态便利记录MySQL内部表级锁定的情况,两个变量说明如下:

table_locks_immediate:产生表级锁定的次数,可以立即获取锁查询的次数,没立即获取锁值加1;

table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况。

此外,myisam引擎的读写锁调度是写优先,这也是myisam不合适做写为主的引擎,因为写锁后,其他线程不能做任何操作,大量更新会使得查询很难得到锁,从而造成永远阻塞。

2.2、行锁

行锁偏向InnoDB引擎,开销大,加锁慢,会出现死锁,锁定粒度小,发生锁冲突的概率最低,并发读也最高。InnoDB引擎与MyISAM引擎的最大不同有两点:一是支持事务(有关事务的内容,参考《MySQL基础》第8节数据库事务),而是采用了行锁。

sql:1


15create table test_innodb_lock(a int(11),b varchar(16)) engine=innodb;
insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);

2.2.1、案例分析

mysql 某条数据被锁 mysql 数据库锁_数据_09

2.2.2、案例结论

加读锁不能阻止其他表的读和写;加写锁,其他可以读,但是不能写。

2.2.3、索引失效行锁升级为表锁

我们之前说过,字符串忘记使用’’或者””会导致索引失效:

mysql 某条数据被锁 mysql 数据库锁_键值_10

对于b字段是varchar型,而上面一条sql语句where中b没有加引号,那么其他连接会话会导致什么问题呢,我们用第二个session去修改其他(非b=4000的记录)的记录:

mysql 某条数据被锁 mysql 数据库锁_键值_11

因为第一个session一直没有提交,导致第二个session等待锁超时了。两个会话根本修改的不是一行记录,因为第一个session索引使用不当而导致行锁表锁。

2.2.4、间隙锁危害

当我们用范围条件而不是相等条件检索数据,并请求共享或者排他锁时,InnoDB会给符合条件的已有数据距离的索引项加上锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Net-Key锁)。

因为Query执行过程中通过范围查找的话,他会锁定整个范围内索引索引键值,即使这个键值不存在,间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据,在某些场景下这可能会对性能造成很大的危害。

我们看到2.2节开始insert数据的,并没有插入a值为2的记录:

mysql 某条数据被锁 mysql 数据库锁_读锁_12

那么现在,我们使用session1将a值为16的b值改为jxswh,session2添加一条记录,a值为2:

mysql 某条数据被锁 mysql 数据库锁_mysql 某条数据被锁_13

session1

mysql 某条数据被锁 mysql 数据库锁_读锁_14

session2

如果session1一值不提交,那么session2一直等锁,最终超时异常。原则上session1修改16的,数据库里并没有2这条记录,但是InnoDB也会将2这个间隙加上锁,其他session也就不能在对该记录做任何写操作。

2.2.5、面试题:如何锁定一行

方式:1

2begin;

select * from test_innodb_lock where a = 8 for update;

begin开始,然后查询需要锁定的一行(使用where条件),最重要的是使用for update结尾,其他操作会被阻塞,知道锁定的行会话commit。

mysql 某条数据被锁 mysql 数据库锁_数据_15

2.2.6、行锁总结

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会高一些。但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。

2.2.7、行锁分析

通过检查innodb状态变量来访恩熙系统上的行锁的争夺情况:1show status like 'innodb_row_lock%'

mysql 某条数据被锁 mysql 数据库锁_mysql 某条数据被锁_16

对个状态的说明如下:Innodb_row_lock_current_waits:当前正在等待锁定的数量

Innodb_row_lock_time:从系统启动到现在锁定的总时间长,单位毫秒,下同

Innodb_row_lock_time_avg:每次等待所花平均时间

Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间

Innodb_row_lock_waits:系统启动后到现在总共等待的次数

对于这5个状态变量,比较重要的是:

尤其是当前等待次数很高,而且每次等待的时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。

3、优化建议

①、尽可能让所有的数据检索都通过索引来完成,避免无索引行锁升级为表锁;

②、合理设计索引,尽量缩小锁的范围;

③、尽可能较少索引条件,避免间隙锁;

④、尽量控制事务大小,减少锁定资源量和时间长度;

⑥、尽可能低级别事务隔离