`

mysql锁问题

 
阅读更多

在一些打折或者抢购活动中,购买商品减少库存量常常存在并发问题,一般来说,我解决这些问题通过两种方式:

1)版本号方式,CAS机制:UPDATE table SET num=num-1 WHERE id=? AND num=?;这种方法适用于并发量比较少的情况。变异版本:UPDATE table SET num=num-1 WHERE id=? AND num>0;

2)行锁:用事务+for update来解决。

2)变成单线程,比如放到队列处理,按先后顺序扔进队列,一个一个处理。比较少用,延迟太高。

 

基本锁概念:共享锁(select * from tabl where ...lock in share mode)、排他锁(select * from table_name where ...for update)。

 

读操作的相关锁:

1)一致性非锁定读:默认事务级别,事务A开启时,select所读取的数据,在事务结束前,即使被其他事务B改变且提交,或者即使新插入一条数据。A会无视期间的所有更新插入(即可重复读,读事务隔离性),读到的数据仍然不变,相当于事务开始时的一个备份。而Read Committed事务级别不同,会读到最新值,这样违反了ACID的I特性。

2)一致性锁定读:默认事务级别,可通过select...for update(x锁,其他事务不能读不能写)或select ... lock in share mode(s锁,其他事务可读不可写)进行锁定,保证数据的逻辑一致性。

       但是,对于一致性非锁定读,即使读取的行已经被其他事务使用了select...for update,仍然可以读到值,因为一致性非锁定读,压根没有锁,直接读取的备份数据。

 

 

锁的实现方式

       在MySQL中,行级锁并不是直接锁记录,而是锁索引(若条件没用到索引,会默认锁主键)。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

       InnoDB行锁是通过给索引项加锁实现的,如果没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁。

也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。

 

行锁的三种算法,详情请看官网

Record lock :行锁,即锁定一条记录,实际是对索引加锁,无索引时默认主键索引。

Gap lock间隙锁,对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录本身。在默认可重复读RR下,防止其他事务操作,防止幻读(写事务隔离性,比如事务A开启select...where ... for update,where条件是非唯一索引则间隙锁、是非索引则锁整个表。如果不加间隙锁,那么事务B提交插入或更新,事务A同条件重新查询,导致数据不一致)。举例:

#这个会有间隙锁
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;

#当id时唯一索引时,没有使用间隙锁。而如果id没有索引,或者非唯一索引,则有间隙锁
SELECT * FROM child WHERE id = 100;

 

Next-key Lock:行锁+间隙锁,锁定一个范围的记录并包含记录本身(上面两者的结合)。一般非主键的索引做条件的用这个。

注意:InnoDB默认级别是repeatable-read级别,所以下面说的都是在RR级别中的。

       Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。如果一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的。

小结:

行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。

何时在InnoDB中使用表锁:

InnoDB在绝大部分情况会使用行级锁,因为事务和行锁往往是我们选择InnoDB的原因,但是有些情况我们也考虑使用表级锁。

1、当事务需要更新大部分数据时,表又比较大,如果使用默认的行锁,不仅效率低,而且还容易造成其他事务长时间等待和锁冲突。

2、事务比较复杂,很可能引起死锁导致回滚。

死锁:参考。可通过查询出主键,再操作。比如update ... where id in(select...)

 

 

实验一:

数据库表结构如下:

CREATE TABLE `user` (
	`Id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NULL DEFAULT NULL,
	`age` INT(11) NULL DEFAULT NULL,
	PRIMARY KEY (`Id`),
	INDEX `Index 2` (`age`)
)
ENGINE=InnoDB;

INSERT INTO `user` (`Id`, `name`, `age`) VALUES (1, 'fgdg', 6);
INSERT INTO `user` (`Id`, `name`, `age`) VALUES (2, 'gdfgd', 9);
INSERT INTO `user` (`Id`, `name`, `age`) VALUES (3, 'gfdg', 12);

 

执行命令如下:

#客户端1操作
set autocommit=0;
select * from user where age=6 for update;

#客户端2操作,这时候会报错ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
insert user values(null,'df',7);

 

实验二:

数据库表结构如下:

CREATE TABLE `t` (
	`Id` INT(11) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR(50) NOT NULL DEFAULT '0',
	`age` INT(11) NOT NULL,
	`workage` INT(11) NOT NULL,
	PRIMARY KEY (`Id`),
	INDEX `Index 2` (`age`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=7;

INSERT INTO `t` (`Id`, `name`, `age`, `workage`) VALUES (1, '0', 2, 1);
INSERT INTO `t` (`Id`, `name`, `age`, `workage`) VALUES (2, 'q2', 6, 8);
INSERT INTO `t` (`Id`, `name`, `age`, `workage`) VALUES (3, 'fd ', 12, 16);

 

执行命令如下:

#客户端一,与客户端二每次每个执行一条语句
mysql> begin;

#顺序------>1
mysql> delete from t where age=6;
Query OK, 1 row affected (0.00 sec)

#顺序------>3,这条语句会阻塞,客户端二报死锁回滚后,才解除死锁。
mysql> insert t values(null,'d',7,0);
Query OK, 1 row affected (42.41 sec)

#客户端二
mysql> begin;

#顺序------>2
mysql> delete from t where age=12;
Query OK, 1 row affected (0.00 sec)

#顺序------>4
mysql> insert t values(null,'d',7,0);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 分析:客户端一锁定age范围为[6,12),客户端二锁定age范围(6,12],所以在(6,12)范围内,两个客户端都不能进行操作。

 

 

死锁总结

1)不同表相同记录行锁冲突

     这种情况很好理解,事务A和事务B操作两张表,但出现循环等待锁情况。

                                                                       图10

2)相同表记录行锁冲突

     这种情况比较常见,之前遇到两个job在执行数据批量更新时,jobA处理的的id列表为[1,2,3,4],而job处理的id列表为[8,9,10,4,2],这样就造成了死锁。

                                                                          图11

3)不同索引锁冲突

     这种情况比较隐晦,事务A在执行时,除了在二级索引加锁外,还会在聚簇索引上加锁,在聚簇索引上加锁的顺序是[1,4,2,3,5],而事务B执行时,只在聚簇索引上加锁,加锁顺序是[1,2,3,4,5],这样就造成了死锁的可能性。

                                                                          图12

3.4 gap锁冲突

     innodb在RR级别下,如下的情况也会产生死锁,比较隐晦。不清楚的同学可以自行根据上节的gap锁原理分析下。
                                                                               图13

如何尽可能避免死锁

1)以固定的顺序访问表和行。比如对第2节两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;又比如对于3.1节的情形,将两个事务的sql顺序调整为一致,也能避免死锁。

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

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

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

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

如何定位死锁成因

     下面以本文开头的死锁案例为例,讲下如何排查死锁成因。

1)通过应用业务日志定位到问题代码,找到相应的事务对应的sql;

      因为死锁被检测到后会回滚,这些信息都会以异常反应在应用的业务日志中,通过这些日志我们可以定位到相应的代码,并把事务的sql给梳理出来。

1
2
3
4
5
start tran
1 deleteHeartCheckDOByToken
2 updateSessionUser
...
commit

      此外,我们根据日志回滚的信息发现在检测出死锁时这个事务被回滚。

2)确定数据库隔离级别。

     执行select @@global.tx_isolation,可以确定数据库的隔离级别,我们数据库的隔离级别是RC,这样可以很大概率排除gap锁造成死锁的嫌疑;

3)找DBA执行下show InnoDB STATUS看看最近死锁的日志。

     这个步骤非常关键。通过DBA的帮忙,我们可以有更为详细的死锁信息。通过此详细日志一看就能发现,与之前事务相冲突的事务结构如下:

1
2
3
4
5
start tran
1 updateSessionUser
2 deleteHeartCheckDOByToken
...
commit

  这不就是第一个图描述的死锁嘛!

 

 

分享到:
评论

相关推荐

    MYSQL锁机制全揭秘

    在解决MySQL锁问题时,需要注意选择合适的事务隔离级别,因为不同级别的隔离,锁的使用情况也不同。此外,合理设计数据库表结构、索引、查询语句等,都有助于减少锁争用,提高性能。 页面锁是BDB存储引擎所采用的一...

    MySQL锁类型以及子查询锁表问题、解锁1

    总的来说,理解和正确使用MySQL的锁机制对于优化并发性能和避免并发问题至关重要。在设计和编写SQL语句时,应尽可能减少锁定范围,提高系统效率。同时,了解如何排查和处理死锁问题也是数据库管理员必备的技能。

    MYSQL锁表问题的解决方法

    本文将介绍几种解决MySQL锁表问题的方法。 1. **查看并杀死锁定进程** 使用`SHOW PROCESSLIST`命令可以查看当前所有正在执行的SQL语句及其状态,包括是否被锁定。如果发现有锁定的进程,可以使用`KILL`命令来终止...

    MYSQL 解锁与锁表介绍

    MySQL锁概述   相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁...

    mysql 锁机制及mysql中的锁介绍.pdf

    系统介绍mysql锁机制

    MYSQL锁机制

    ### MySQL锁机制详解 #### 一、MySQL锁机制概述 MySQL中的锁机制是数据库管理系统用于控制并发事务对数据资源访问的关键技术之一。合理地管理和利用锁机制能够有效地防止数据不一致性和提高系统的并发处理能力。...

    MySQL的锁机制解析

    MySQL的锁机制是数据库管理系统中用于控制并发操作的...理解MySQL的锁机制对于优化数据库性能、解决并发问题和确保数据一致性至关重要。通过深入学习和实践,我们可以更好地管理和维护MySQL数据库,提高系统整体效率。

    Linux系统安装MySQL数据库

    MySQL 数据库是一个非常重要的步骤,这需要了解 MySQL 的体系结构、应用优化、常用工具、索引存储引擎、查询缓存优化、视图优化、SQL 步骤、内存管理及优化、主从复制、存储过程和函数、索引使用、MySQL 锁问题综合...

    MySQL锁详解

    MySQL的锁机制对于数据库系统的性能和稳定性起着至关重要的作用。在数据库设计和开发过程中,合理地使用锁策略,能够有效避免死锁现象,提高并发处理能力。接下来,将详细介绍MySQL中的不同类型的锁及其适用场景。 ...

    59 对MySQL锁机制再深入一步,共享锁和独占锁到底是什么?l.pdf

    在深入探讨MySQL数据库的锁机制之前,我们首先需要了解锁的基本概念。在数据库系统中,锁是一种用来保障并发控制的机制,它用来协调不同事务对同一个数据对象的访问。锁的目的是为了防止多个事务并发操作时造成的...

    MySQL8.0锁机制和事务

    MySQL 8.0 锁机制和事务 MySQL 8.0 中的锁机制和事务是数据库系统中非常重要的概念。锁机制是指数据库系统中对数据访问的控制机制,而事务是指数据库系统中的一组操作单元。了解锁机制和事务是非常关键的,因为它们...

    MySQL锁与事务脑图.pdf

    MySQL锁与事务知识脑图

    Mysql(MyISAM)的读写互斥锁问题的解决方法

    "Mysql(MyISAM)的读写互斥锁问题的解决方法" 在Mysql(MyISAM)中,读写互斥锁问题是常见的性能瓶颈之一。为了解决这个问题,需要了解MyISAM的读写机制和锁机制。MyISAM在读操作占主导的情况下是很高效的,但是一旦...

    MySQL:锁机制.pdf

    本知识点详细解读了MySQL锁机制的定义、分类、特点以及具体的应用案例。 1. 锁的定义和分类 在数据库系统中,锁是用来控制多个用户或进程对同一数据资源进行访问的同步机制。它解决的是多个操作同时对同一资源进行...

    mysql锁详解

    资源名称:mysql锁详解资源截图: 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。

    行业-59 对MySQL锁机制再深入一步,共享锁和独占锁到底是什么?l.rar

    死锁是锁机制可能导致的问题之一。当两个或更多事务相互等待对方释放锁时,就会发生死锁。MySQL通过检测和回滚其中一个事务来解决死锁,以打破循环等待。为避免死锁,开发人员需要谨慎设计事务的执行顺序,并合理...

    MySQL悲观锁总结和实践

    ### MySQL悲观锁总结和实践 #### 一、悲观锁概念详解 悲观锁是一种同步机制,其基本思想是对数据被外界修改持有悲观态度,因此在整个数据处理过程中,将数据处于锁定状态。简而言之,悲观锁认为数据在处理过程中很...

Global site tag (gtag.js) - Google Analytics