一个关于Mysql事务的罕见问题

博主开发游戏服务器副本模块时,数据库存储使用事务出现Mysql deadlock问题。介绍了表结构和数据更新机制,启动一万个机器人发包后数据库常报死锁错误。经研究发现,问题关键在于没理解innodb锁机制,因charGuid分布在同一page,获取意向锁冲突导致死锁。
最近负责开发一个游戏服务器,当做到副本模块时数据库的存储部分用了事务,结果就出现了一个Mysql deadlock的问题。
   首先给出表结构:
            CREATE TABLE IF NOT EXISTS `t_instance` (
              `charGuid` bigint(20) unsigned NOT NULL DEFAULT '0',
              `majorInsId` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '大副本ID',
              `minorInsId` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '中副本ID',
              `instanceId` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '小副本ID',
              `hardType` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '难度类型',
              `status` int(10) unsigned NOT NULL DEFAULT '0',
              `star` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '最大星级',
              `joinCount` smallint(6) NOT NULL DEFAULT '0' COMMENT '今日闯关次数',
              KEY `charGuid_Normal` (`charGuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='副本';
可以看到这个表中只有一个普通键charGuid,按照常理来讲,可能应该是一个联合主键,包括( charGuid,majorInsId,minorInsId,instanceId,hardType),当然这和我的数据更新机制有关,不需要这么复杂的键。在陈述问题之前先要说明的是,对于任意玩家,instance记录的条数没有数量限制。
接下来给出我的数据更新机制:
         首先启动事务,运行delete from t_instance where charGuid=xxx,然后再运行数条insert语句把内存中的数据插入进来,然后提交事务,如果有意外的情况发生,则回滚事务,每个玩家大概每隔5分钟更新一次数据。另外重要的一点是,配置了多个数据库线程,每个线程都负责了特定一批玩家的数据更新。比如说线程1,负责奇数号的玩家数据更新,线程2负责了偶数号的玩家的数据更新。再解释下innodb引擎关于事务使用了行锁,也就是说根据索引进行锁定,比如可以只锁定charGuid为1的数据,这是一种粒度很细的锁。
        代码完成之后,我启动了大概一万个机器人不停发包打副本,然后在数据库一层经常报事务的deadlock错误,然后我查了一下mysql的状态,这里给出一下状态日志:
        LATEST DETECTED DEADLOCK
------------------------
2014-09-01 11:23:26 a38
*** (1) TRANSACTION:
TRANSACTION 84585427, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 17, OS thread handle 0x948, query id 646279 localhost 127.0.0.1 yuwenyong update
insert into t_instance set charGuid=5379,majorInsId=1,minorInsId=1,instanceId=0,hardType=1,status=0,star=0,joinCount=0
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 193 page no 59 n bits 632 index `charGuid_Normal` of table `shqzdb`.`t_instance` trx id 84585427 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 557 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 0000000000001504; asc         ;;
 1: len 6; hex 0000002feb62; asc    / b;;


*** (2) TRANSACTION:
TRANSACTION 84585428, ACTIVE 0 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1
MySQL thread id 15, OS thread handle 0xa38, query id 646280 localhost 127.0.0.1 yuwenyong update
insert into t_instance set charGuid=5377,majorInsId=1,minorInsId=1,instanceId=0,hardType=1,status=0,star=0,joinCount=0
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 193 page no 59 n bits 632 index `charGuid_Normal` of table `shqzdb`.`t_instance` trx id 84585428 lock_mode X locks gap before rec
Record lock, heap no 557 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 0000000000001504; asc         ;;
 1: len 6; hex 0000002feb62; asc    / b;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 193 page no 59 n bits 632 index `charGuid_Normal` of table `shqzdb`.`t_instance` trx id 84585428 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 557 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 8; hex 0000000000001504; asc         ;;
 1: len 6; hex 0000002feb62; asc    / b;;


*** WE ROLL BACK TRANSACTION (2) 


        日志的意思是说,在插入5377和5379的用户的副本数据时出现了锁冲突,我当时配置了4个数据库线程,根据我的分配算法,5377和5379是在不同的数据库线程中更新数据的,当然也使用了不同的数据库连接。但是死锁如何产生,让我怎么都想不通。后来各种网上查资料,并加了一些mysql dba群询问个中秘密,竟也无人能道出其中的原因。 后来只能自己慢慢研究,研读日志,终于明白了其中的问题所在。
        问题的关键在于没能详细理解innodb锁的机制,仔细观察上面的日志,有一句话很重要: RECORD LOCKS space id 193 page no 59 n bits 632 index `charGuid_Normal` of table `shqzdb`.`t_instance` trx id 84585428 lock_mode X locks gap before rec insert intention waiting。这里出现了几个关键词:page, lock_mode X,和intention waiting。原来innodb为了提高性能,不仅仅是简单的使用了行锁,当进行数据更新时,在page一层使用了一种叫意向锁的东西。当你要获取排他锁的时候(lock_mode x),必须先获取意向锁。mysql如此设计的初衷是为了性能,不必每次都到row级别去检测冲突。这里charGuid 5377和5379分布在同一个page,后运行的事务试图获取意向锁的时候产生了冲突,导致mysql deadlock的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值