web系统常见并发问题
说说考试系统中的一些并发问题吧,这几天着重解决了一下题目这边的并发问题。
其实并发问题不外乎就是:
1.当插入一道试题时,结果这个题目所属的课程被删除掉了;
2.当删除一个课程时,先查时这个课程下的试题没有被使用可以被删除,结果在将将要执行删除语句之前,有一道试题被使用了;
对于第一个问题,其实用外键就可以解决。但是公司似乎有规定,不能够使用外键。主要原因还是外键对于数据库性能的损耗比较严重,特别是当数据量较大时。
其实外键对于我们这个考试系统也是不好使的,因为系统中的删除都是逻辑删除,只是将is_delete置为1而已。外键不好使怎么办?mysql似乎写了check约束也不执行,怎么办?
我开始采用的是触发器的方法。当插入一道试题时,就在course中检查这个courseId的有效性。但这个解决方法并不好,因为即使在触发器中发现该courseId已经无效也不能够中断插入语句,我所做是将插入的新的试题的is_delete字段置为1.也就是说插入的是一道无效试题。
CREATE TRIGGER CheckCourse_BeforeInsert BEFORE INSERT ON question
FOR EACH ROW
BEGIN
if EXISTS(SELECT course.id FROM course WHERE course.id = New.course_id AND course.is_delete = 1) Then
SET New.is_delete = 1;
END IF;
END;
今天上午经过和DBA吕哥进行沟通,它建议我将select和insert做成一个事务,即先查课程有没有效,有效再继续插入。但是select是不锁表的,所以这里的select是select **** for update。它能够将查询的这个课程id这一行锁住。保证在整个事务内,这一行是不会被修改的。
SELECT course.is_delete AS isDelete FROM course WHERE course.id = #{courseId} FOR UPDATE;
对于第二个问题,一般就采用乐观锁的思想。我的具体实现是,在执行之前先查询有多少条将要被删除掉,然后执行删除语句,返回实际删除的行数,当发现不一致时,抛出runtimeException进行回滚。
其实这些并发问题可以说是任何一个web系统都会遇到的问题。最好的解决方案,应该是版本号机制。
版本号机制就是为数据库的每一条记录打上版本,每次修改了该记录,版本号加一,每次查询的结果都携带一个版本号。以第二个例子为例,先查询出所有要删除的题目的id,并且携带上版本号,当删除时发现版本不一致时,就表示该条记录被修改过了,进行处理,如抛出异常回滚等。在本次考试系统的数据库中,每一张表都有一个updateTime字段,就可以用来作为版本号。
PS:Spring中的用@Trancational注解标记的方法互相嵌套调用时与是否是同一个线程,同一个类没有关系。只要是Trancational方法,它就会根据propagation属性的值去决定是创建一个新的事务还是加入已存在的事务中。