在使用事务时,出现以下状况的原因

cztonline
  • 368

需求:记录用户的第一条访问信息到日志表。

日志表为:log_db

代码如下

//开启事务
beginTrans();
try{
  //查询日志表中是否有数据(数据库操作)
  logData = dbFunc('select * from log_db where user_id=255');
  if(logData为空){  //不存在该用户的数据
    //向数据库添加
    dbFunc('insert into log_db VALUES (255,xxx,yyy,zzz,nnn)');
  }
  //提交事务
  commit();
}catch($e){
  //回滚事务
  rollback();
}

功能上线后,本以为仅能收到一条user_id为255的访问信息,没想到却收到一堆:
收到的错误结果如下:
log_db表中出现了n条user_id为255的数据:

255,xxx,yyy,zzz,nnn,'20210619_1724'
255,xxx,yyy,zzz,nnn,'20210619_1724'
255,xxx,yyy,zzz,nnn,'20210619_1724'
255,xxx,yyy,zzz,nnn,'20210619_1724'

而且创建时间都是同一秒,应该是并发执行了。
这是为什么?如何解释?

回复
阅读 1.2k
6 个回答

更好的方法不是加锁。

按照你的需求来说,其实根本不必加锁,利用数据库的特性就行:
1、对表增加一个主键,每天用户访问的第一条,默认设置主键为 ${userId}-yyyy-MM-dd
2、首先对这个主键进行查询
3、如果查询不到,就插入

这样只有第一条能够插入,其它都会失败,并不需要用到锁,减少了代码量,并且消耗是最小的。

如果是每小时、每周等等同理

你这是把数据库事务当锁用么……

从你这个业务逻辑来看,你事务的隔离级别得设成 SERIALIZABLE 才行。但这太重了,等于完全牺牲了数据库的并发能力,一般生产而言都是引入专门的锁机制的。

答案

不要使用事务直接使用下面语句试试:
使用 insert ignore into tb_name values('vl','vl'); //ignore 如果有重复,取消当前值插入(当插入的值遇到主键PRIMARY KEY或者唯一键(UNIQUE KEY)重复时自动忽略重复的记录行,不影响后面的记录行的插入)

下面是测试内容

mysql > desc test;

FieldTypeNullKeyDefaultExtra
idintNOPRINULL
namechar(50)YES NULL

mysql > insert into test values(1,'zhao');

mysql > select * from test;

idname
1zhao
mysql> insert ignore into test values(1,'wang');     //ignore 如果有重复,取消当前值插入(  当插入的值遇到主键PRIMARY KEY或者唯一键(UNIQUE KEY)重复时自动忽略重复的记录行,不影响后面的记录行的插入)

mysql> select * from test;

+----+------+
| id | name |
+----+------+
|  1 | zhao |
+----+------+

留给你的问题😏

下面是t1的表结构
mysql> desc t1;
+-------+--------------+------+-----+-------------------+-------------------+
| Field | Type         | Null | Key | Default           | Extra             |
+-------+--------------+------+-----+-------------------+-------------------+
| id    | int unsigned | NO   | PRI | NULL              | auto_increment    |
| name  | varchar(50)  | YES  |     | NULL              |                   |
| dt    | datetime     | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+-------+--------------+------+-----+-------------------+-------------------+

下面是一些sql语句

begin;

-- 这里做一些其他事情

insert into t1(name) value(myNameVar);

commit;

问题: 当有多个连接同时执行上面sql语句完成时, t1表的记录中有没有可能产生 至少两条id 字段相同的记录 mysql数据库,且隔离等级为 可重复读

没加锁 没幂等 没有防重入

相同的请求被并发执行了呗

最简单的办法

按入参加锁

先判断,在执行。存在并发中的竟态条件问题。即logData 判断的时候还为空,但刚判断成功,它就不为空了。办法加锁,悲观锁还是乐观锁随你。又看了一下题目,user_id好像并不是主键,也不是唯一键,所以可重复读好像也解决不了你的问题。而且可重复读在为空的时候好像依然存在并发问题。还是老老实实以user_id为锁吧。串行化也行,就是影响了整个数据库的效率。

事物的特性:原子性,一致性,隔离性,持久性;
并发事务带来的问题有:更新丢失、脏读、不可重复读、幻读;
题主的问题应该是类似更新丢失的一种情况,即:最后的操作覆盖了其他事务之前的操作,而事务之间并不知道,发生更新丢失。
一般的解决办法是手动加锁,即对事务操作进行手动判断:
1.乐观锁思想(版本号控制,即数据库加上版本号version字段)
2.悲观锁:关闭mysql数据库的自动提交属性,先锁数据再修改(对效率影响较大,容易产生死锁)

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏