如何解决多线程造成的数据库死锁

如何解决多线程造成的数据库死锁,第1张

多线程是很容易造成死锁,一般情况下死锁都是因为并发 *** 作引起的。我不懂JAVA,但死锁这个问题每种开发工具和数据库都会碰到解决办法是:

1、程序方面优化算法(如有序资源分配法、银行算法等),在一个程序里,能不用多线程更新同一张数据库表

尽量不要用,如果要用,其避免死锁的算法就很复杂。

2、数据库方面设置等待超时时间

3、发生死锁后直接KILL掉数据库进程

MySQL死锁问题的相关知识是本文我们主要要介绍的内容,接下来我们就来一一介绍这部分内容,希望能够对您有所帮助。

1、MySQL常用存储引擎的锁机制

MyISAM和MEMORY采用表级锁(table-level locking)

BDB采用页面锁(page-level locking)或表级锁,默认为页面锁

InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

2、各种锁特点

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

3、各种锁的适用场景

表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用

行级锁则更适合于有大量按索引条件并发更新数据,同时又有并发查询的应用,如一些在线事务处理系统

4、死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

表级锁不会产生死锁。所以解决死锁主要还是针对于最常用的InnoDB。

5、死锁举例分析

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

在UPDATE、DELETE *** 作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

例如,一个表db。tab_test,结构如下:

id:主键;

state:状态;

time:时间;

索引:idx_1(state,time)

出现死锁日志如下:

(1) TRANSACTION:

TRANSACTION 0 677833455, ACTIVE 0 sec, process no 11393, OSthread id 278546 starting index read

mysql tables in use 1, locked 1

LOCK WAIT 3 lock struct(s), heap size 320

MySQL thread id 83, query id 162348740 dcnet03 dcnet Searching rows for update

update tab_test set state=1064,time=now() where state=1061 and time < date_sub(now(), INTERVAL 30 minute) (任务1的sql语句)

(1) WAITING FOR THIS LOCK TO BE GRANTED: (任务1等待的索引记录)

RECORD LOCKS space id 0 page no 849384 n bits 208 index `PRIMARY` of table `db/tab_test` trx id 0 677833455 _mode X locks rec but not gap waiting

Record lock, heap no 92 PHYSICAL RECORD: n_fields 11; compact format; info bits 0

0: len 8; hex 800000000097629c; asc b ;; 1: len 6; hex 00002866eaee; asc (f ;; 2: len 7; hex 00000d40040110; asc @ ;; 3: len 8; hex 80000000000050b2; asc P ;; 4: len 8; hex 800000000000502a; asc P;; 5: len 8; hex 8000000000005426; asc T&;; 6: len 8; hex 800012412c66d29c; asc A,f ;; 7: len 23; hex 75706c6f6164666972652e636f6d2f6 8616e642e706870; asc xxxcom/;; 8: len 8; hex 800000000000042b; asc +;; 9: len 4; hex 474bfa2b; asc GK +;; 10: len 8; hex 8000000000004e24; asc N$;;

(2) TRANSACTION:

TRANSACTION 0 677833454, ACTIVE 0 sec, process no 11397, OS thread id 344086 updating or deleting, thread declared inside InnoDB 499

mysql tables in use 1, locked 1

3 lock struct(s), heap size 320, undo log entries 1

MySQL thread id 84, query id 162348739 dcnet03 dcnet Updating update tab_test set state=1067,time=now () where id in (9921180) (任务2的sql语句)

(2) HOLDS THE LOCK(S): (任务2已获得的锁)

RECORD LOCKS space id 0 page no 849384 n bits 208 index `PRIMARY` of table `db/tab_test` trx id 0 677833454 lock_mode X locks rec but not gap

Record lock, heap no 92 PHYSICAL RECORD: n_fields 11; compact format; info bits 0

0: len 8; hex 800000000097629c; asc b ;; 1: len 6; hex 00002866eaee; asc (f ;; 2: len 7; hex 00000d40040110; asc @ ;; 3: len 8; hex 80000000000050b2; asc P ;; 4: len 8; hex 800000000000502a; asc P;; 5: len 8; hex 8000000000005426; asc T&;; 6: len 8; hex 800012412c66d29c; asc A,f ;; 7: len 23; hex 75706c6f6164666972652e636f6d2f6 8616e642e706870; asc uploadfirecom/handphp;; 8: len 8; hex 800000000000042b; asc +;; 9: len 4; hex 474bfa2b; asc GK +;; 10: len 8; hex 8000000000004e24; asc N$;;

(2) WAITING FOR THIS LOCK TO BE GRANTED: (任务2等待的锁)

RECORD LOCKS space id 0 page no 843102 n bits 600 index `idx_1` of table `db/tab_test` trx id 0 677833454 lock_mode X locks rec but not gap waiting

Record lock, heap no 395 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 8; hex 8000000000000425; asc %;; 1: len 8; hex 800012412c66d29c; asc A,f ;; 2: len 8; hex 800000000097629c; asc b ;;

WE ROLL BACK TRANSACTION (1)

(回滚了任务1,以解除死锁)

原因分析:

当“update tab_test set state=1064,time=now() where state=1061 and time < date_sub(now(), INTERVAL 30 minute)”执行时,MySQL会使用idx_1索引,因此首先锁定相关的索引记录,因为idx_1是非主键索引,为执行该语句,MySQL还会锁定主键索引。

假设“update tab_test set state=1067,time=now () where id in (9921180)”几乎同时执行时,本语句首先锁定主键索引,由于需要更新state的值,所以还需要锁定idx_1的某些索引记录。

这样第一条语句锁定了idx_1的记录,等待主键索引,而第二条语句则锁定了主键索引记录,而等待idx_1的记录,这样死锁就产生了。

6、解决办法

拆分第一条sql,先查出符合条件的主键值,再按照主键更新记录:

select id from tab_test where state=1061 and time < date_sub(now(), INTERVAL 30 minute);

update tab_test state=1064,time=now() where id in();

多线程开启事务处理。每个事务有多个update *** 作和一个insert *** 作(都在同一张表)。

默认隔离级别:Repeatable Read

只有hotel_id=2和hotel_id=11111的数据

逻辑删除原有数据

插入新的数据

根据现有数据情况,update的时候没有数据被更新

报了非常多一样的错

发现居然有死锁。

根据常识考虑,我每个线程(事务)更新的数据都不冲突,为什么会产生死锁?

带着这个问题,打印mysql最近一次的死锁信息

show engine innodb status

显示如下

发现事务1在等待一个锁

事务2也在等待一个锁

而且事物2持有了事物1需要的锁

关于锁的描述,出现了 lock_mode gap before rec insert intention 等字眼,看不懂说明了什么?说明我关于mysql的锁相关的知识储备还不够。那就开始调查mysql的锁相关知识。

通过搜索引擎,

锁的持有兼容程度如下表

那么再回到死锁日志,可以知道 :

事务1正在获取插入意向锁

事务2正在获取插入意向锁,持有排他gap锁

再看我们上面的锁兼容表格,可以知道, gap lock和insert intention lock是不兼容的

那么就可以推断出: 事务1持有gap lock,等待事务2的insert intention lock释放;事务2持有gap lock,等待事务1的insert intention lock释放,从而导致死锁。

那么新的问题就来了,事务1的intention lock 为什么会和事务2的gap lock 有交集,或者说,事务1要插入的数据的位置为什么会被事务2给锁住?

让我回顾一下gap lock的定义:

间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况

那为什么是gap lock,gap lock到底是基于什么逻辑锁的记录?发现自己相关的知识储备还不够。那就开始调查。

调查后发现,当当前索引是一个 普通索引 的时候,会加一个gap lock来防止幻读, 此gap lock 会锁住一个左开右闭的区间。 假设索引为xx_idx(xx_id),数据分布为1,4,6,8,12,当更新xx_id=9的时候,这个时候gap lock的锁定记录区间就是(8,12],也就是锁住了xxid in (9,10,11,12)的数据,当有其他事务要插入xxid in (9,10,11,12)的数据时,就会处于等待获取锁的状态。

ps:当前索引不是普通索引,而且是唯一索引等其他情况,请参考下面资料

MySQL 加锁处理分析

回到我自己的案例中,重新屡一下事务1的执行过程:

因为普通索引

KEY hotel_date_idx ( hotel_id , rate_date )

的关系 这段sql会获取一个gap lock,范围(2,11111]

这段sql会获取一个insert intention lock (waiting)

再看事务2的执行过程

因为普通索引

KEY hotel_date_idx ( hotel_id , rate_date )

的关系 这段sql也会获取一个gap lock,范围也是(2,11111](根据前面的知识,gap lock之间会互相兼容,可以一起持有锁的)

这段sql也会获取一个insert intention lock (waiting)

看到这里,基本也就破案了。因为普通索引的关系,事务1和事务2的gap lock的覆盖范围太广,导致其他事务无法插入数据。

重新梳理一下:

所以从结果来看,一堆事务被回滚,只有10007数据被更新成功

gap lock 导致了并发处理的死锁

在mysql默认的事务隔离级别(repeatable read)下,无法避免这种情况。只能把并发处理改成同步处理。或者从业务层面做处理。

共享锁、排他锁、意向共享、意向排他

record lock、gap lock、next key lock、insert intention lock

show engine innodb status

Oracle数据库出现死锁的时候可以按照以下处理步骤加以解决:

第一步:尝试在sqlplus中通过sql命令进行删除,如果能够删除成功,则万事大吉!但通常情况下,出现死锁时,想通过命令行或者通过Oracle的管理工具删除有死锁的session,oracle只会将该session标记为killed,但无法清除掉,往往需要通过第二步在 *** 作系统层级进行删除!

Connected to Oracle9i Enterprise Edition Release 92010 

Connected as quik

SQL> select xidusn, object_id, session_id, locked_mode from v$locked_object; --查死锁的对象,获取其SESSION_ID

XIDUSN OBJECT_ID SESSION_ID LOCKED_MODE

---------- ---------- ---------- -----------

10 30724 29 3

10 30649 29 3

SQL> select username,sid,serial# from v$session where sid=29; --根据上步获取到的sid查看其serial#号

USERNAME SID SERIAL#

------------------------------ ---------- ----------

QUIK 29 57107

SQL> alter system kill session '29,57107'; --删除进程,如已经删除过,则会报ora-00031的错误;否则oracle会将该session标记为killed状态,等待一段时间看能否会自动消失,如长时间消失不掉,则需要做后续步骤

alter system kill session '29,57107'

ORA-00031: session marked for kill

SQL> select prospid from v$session ses,v$process pro where sessid=29 and sespaddr=proaddr; --查看spid号,以便在 *** 作系统中根据该进程号删除进程

SPID

------------

2273286

第二步:进入 *** 作系统进行删除进程,本示例的 *** 作系统是IBM aix。

login: root --录入用户名

root's Password: --录入密码

 

 

 Welcome to AIX Version 53! 

 

 

 Please see the README file in /usr/lpp/bos for information pertinent to 

 this release of the AIX Operating System 

 

 

Last unsuccessful login: Fri Apr 23 14:42:57 BEIDT 2010 on /dev/pts/1 from 1073

52254

Last login: Fri Apr 23 15:27:50 BEIDT 2010 on /dev/pts/2 from 107352254

# ps -ef|grep 2273286 --查看进程详情

root 2289864 2494636 0 17:07:15 pts/1 0:00 grep 2273286

oracle 2273286 1 0 14:38:24 - 0:21 oracleQUIK (LOCAL=NO)

# kill -9 2273286 --删除进程,小心 *** 作,别写错进程号,如果oracle的关键进程被删,数据库会崩溃的!

# ps -ef|grep 2273286 --再次查看

root 2289864 2494636 0 17:07:15 pts/1 0:00 grep 2273286

For Windows, at the DOS Prompt: orakill sid spid

For UNIX at the command line> kill –9 spid

以上就是关于如何解决多线程造成的数据库死锁全部的内容,包括:如何解决多线程造成的数据库死锁、用sql语句,怎么解决mysql数据库死锁、解决一次mysql死锁问题等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/sjk/9853147.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-02
下一篇2023-05-02

发表评论

登录后才能评论

评论列表(0条)

    保存