MSSqlServer Select与Insert高并发下的死锁

原创,2015-09-29

目录

1 事件回放
2 DB Trace线索整理
3 Log线索整理
4 当时的数据库配置说明
5 原因分析一(查询事务隔离级别应该为READ UNCOMMITTED)
6 原因分析二(确认数据库的锁是死锁还是活锁)
7 目前解决方案
8 遗留及仍需深入分析的问题

事件回放

客服和市场部反应积分游戏-大转盘出现无法查询“投注记录”,点击抽奖后一直超时导致球进入问号区域无法获知抽奖结果。
随后接到系统室的通知,数据库层面有大量死锁,CPU利用率已达到瓶颈值,随后跟市场部联系将游戏入口切换到升级页面,停掉应用,迅速协查原因。

BI报表和Zabbix监控

BI 2015-05-04

BI 2015-05-05

Zabbix.jpg

接口响应时间阻塞严重

请求路径 请求笔数 响应时间平均 响应时间90 响应时间最大
getBetRecords.json 21,318 37,399 53,257 133,631
drawLottery.json 58,458 28,811 50,172 132,225

DB Trace线索整理

请参见文件:Database-Dump.zip

关注一下几处关键点
引起死锁的源头:

-- headblockersummary --
select top 6 lelog0_.log_id as log1_3_, lelog0_.log_activity_code as log2_3_, lelog0_.log_bet_actor_code as log3_3_, lelog0_.log_bet_actor_count as log4_3_, lelog0_.log_bet_actor_name as log5_3_, lelog0_.log_bet_actor_type as log6_3_, lelog0_.log_cmb_accou

io成本比较高:

plan_total_logical_readsplan_total_exec_count

Log线索整理

大量的数据库事务回滚

org.hibernate.TransactionException: JDBC rollback failed
at org.springframework.orm.hibernate3.HibernateTransactionManager.doRollback(HibernateTransactionManager.java:679)

Caused by: org.hibernate.TransactionException: JDBC rollback failed
at org.hibernate.transaction.JDBCTransaction.rollback(JDBCTransaction.java:170)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doRollback(HibernateTransactionManager.java:676)
... 89 more
Caused by: java.sql.SQLException: Connection has already been closed.
at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:117)
at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:109)

大量的数据库连接关闭

2015-05-06 08:24:15,073 [catalina-exec-30] [org.hibernate.jdbc.AbstractBatcher] [WARN] [280] - exception clearing maxRows/queryTimeout

com.microsoft.sqlserver.jdbc.SQLServerException: The connection is closed.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.checkClosed(SQLServerConnection.java:388)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.checkClosed(SQLServerStatement.java:978)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getMaxRows(SQLServerStatement.java:1011)

大量的Socket连接关闭

org.springframework.dao.DataAccessResourceFailureException: could not insert: [com.cmbchina.ccd.pluto.lotteryengine.models.LeLog]; nested exception is org.hibernate.exception.JDBCC
onnectionException: could not insert: [com.cmbchina.ccd.pluto.lotteryengine.models.LeLog]
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:625)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:412)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:411)
Caused by: org.hibernate.exception.JDBCConnectionException: could not insert: [com.cmbchina.ccd.pluto.lotteryengine.models.LeLog]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:74)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:40)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2163)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2643)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:51)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Socket closed
at com.microsoft.sqlserver.jdbc.SQLServerConnection.terminate(SQLServerConnection.java:1667)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.terminate(SQLServerConnection.java:1654)
at com.microsoft.sqlserver.jdbc.TDSChannel.read(IOBuffer.java:1789)

当时的数据库配置说明

DB Version : Microsoft SqlServer 2008
Le_Log日志表创建的索引为:

聚集 IDX_ACTIVITY_ACCOUNTID = activityCode + accountId + logUpdateAt
非聚集 IDX_ACTIVITY_USREID = activityCode + userId + logUpdateAt

日志查询语句及执行计划

SELECT top 6 * FROM LE_LOG WHERE activityCode=? AND userId=? AND updateAt BETWEEN ? AND ?

INDEX Seek 非聚集索引 IDX_ACTIVITY_USREID 找到对应的rowId
INDEX Seek 聚集索引 IDX_ACTIVITY_ACCOUNTID 根据rowId找到对应行其他列的数据

日志插入

INSERT INTO LE_LOG VALUES();
先拿到聚集索引的排它锁,将要插入的行进行物理排序,然后对非聚集索引进行维护

原因分析一(查询事务隔离级别应该为READ UNCOMMITTED)

研究原文参考:
CSDN-sql server中高并发情况下 同时执行select和update语句死锁问题 (一)
CSDN-sql server中高并发情况下 同时执行select和update语句死锁问题 (二)

在本文的场景中,问题可能就出在非聚集索引与聚集索引的死锁问题上

步骤 Select过程 Insert过程
1 Select获取到对应用户抽奖记录的行共享S锁拿到rowId
2 Insert获取聚集索引的排它X锁
3 根据rowId试图获取聚集索引的共享S锁查询其他列的数据,阻塞!
4 Insert需要同步更新非聚集索引,试图获取排它锁X,被S锁阻塞!

所以死锁发生了。。。
大量的事务回滚应该是为了防止死锁造成数据库宕机设置的锁超时机制造成的,一旦无法插入或者查询日志记录就出现了大量的回滚或者连接强制断开,也就是我们在异常日志中看到的。而事务回滚占用的资源量很大,结果就是系统CPU利用率不断升高。

原因分析二(确认数据库的锁是死锁还是活锁)

CSDN-死锁与活锁 //这个暂时没有研究透彻,作为问题保留!

目前解决方案

  1. SQL语句添加 WITH (NOLOCK) 允许脏读HQL语句Service的方法前添加
    @Transactional(isolation=ISOLATION.READ_UNCOMMITTED)
  1. 将userId原本的非聚集索引修改为聚集索引, accountId原本的聚集索引改为非聚集索引。这样投注记录的select查询将只走聚集索引seek。
  2. 将select * from 修改为select 明确的列 from, 减少io数据量
  3. 清理数据库:目前抽奖记录因为市场部的强烈要求必须保留3个月,所以数据量无法缩减。下期新游戏上线的时候会将投注记录UI的提示字样改为一个月,然后同步修改数据库清理脚本为只保留一个月数据。
  4. 在查询投注记录明细的时候排序采用的字段是log_id desc,不是聚集索引中的log_update_at,导致执行计划中有很大一部分的排序性能消耗,修改为按照更新时间倒序。当初采用log_id排序是因为测试过程中发现抽奖过快会出现相同时间戳记录的出现,当然这种概率极低。考虑性能输出,对于相同时间戳的记录排序混乱可以忍受。

遗留及仍需深入分析的问题

  1. 死锁活锁的特征分析
  2. 应用运行三个月期间一直很稳定,当天的并发请求量通过对比也没有发现异常的峰值,为什么这个死锁会突然出现,触发条件到底是什么? 仅仅是因为数据量的不断积累导致的么?

Any Remarks & Suggestions are welcomed~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容