做网站总费用,营销管理软件,商务网站主页设计公司,二建咨询在线在最近的开发中#xff0c;碰到一个需求签到#xff0c;每个用户每天只能签到一次#xff0c;那么怎么去判断某个用户当天是否签到呢#xff1f;因为当属表设计的时候#xff0c;每个用户签到一次#xff0c;即向表中插入一条记录#xff0c;根据记录的数量和时间来判断…在最近的开发中碰到一个需求签到每个用户每天只能签到一次那么怎么去判断某个用户当天是否签到呢因为当属表设计的时候每个用户签到一次即向表中插入一条记录根据记录的数量和时间来判断用户当天是否签到。这样的话就会有一个问题如果是在网速过慢的情况下用户多次点击签到按钮那么变会发送多次请求可能会导致一天多次签到重复提交的问题那么很自然的想到用事务。这次用的是spring mybtais的框架一开始设计的代码大致如下public booleansignIn(SignInHistory signInHistory) {//编程式开启事务TransactionTemplate template newTransactionTemplate(transactionManager);boolean result (boolean) template.execute(new TransactionCallback() {publicObject doInTransaction(TransactionStatus transactionStatus) {try{//获取用户所有签到记录List SignInHistoryList signInMapper.select(signInHistory);//如果当前时间和List中某条签到时间相同则当天已签到代码略去//插入签到历史表signInMapper.insert(signInHistory);}catch(Exception e) {transactionStatus.setRollbackOnly();logger.error(e);return false;}return true;}});}但是在测试中发现还是会发生重复提交。那么看mysql文档Consistent read is the default mode in whichInnoDBprocessesSELECTstatements inREAD COMMITTEDandREPEATABLE READisolation levels. A consistent read does not set any locks on the tables it accesses, and therefore other sessions are free to modify those tables at the same time a consistent read is being performed on the table.Mysql文档中也有相关说明如果是在read committed和repeatab read下普通的select语句并不会进行锁操作。其它session可以照常更新或插入操作。所以在这里面就可以发现如果只是普通select不管在不在事务中mysql都不会将select加锁所以根本无法阻止其它事务插入记录。由此可以得出一个理解事务隔离级别数据库事务隔离级别只是针对一个事务能不能读取其它事务的中间结果。Read Uncommitted(读取未提交内容)在该隔离级别所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用因为它的性能也不比其他级别好多少。读取未提交的数据也被称之为脏读(Dirty Read)。Read Committed(读取提交内容)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read)因为同一事务的其他实例在该实例处理其间可能会有新的commit所以同一select可能返回不同结果。Repeatable Read(可重读)这是MySQL的默认事务隔离级别它确保同一事务的多个实例在并发读取数据时会看到同样的数据行。不过理论上这会导致另一个棘手的问题幻读(Phantom Read)。简单的说幻读指当用户读取某一范围的数据行时另一个事务又在该范围内插入了新行当用户再读取该范围的数据行时会发现有新的幻影行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCCMultiversion Concurrency Control)机制解决了该问题。Serializable(可串行化)这是最高的隔离级别它通过强制事务排序使之不可能相互冲突从而解决幻读问题。简言之它是在每个读的数据行上加上共享锁。在这个级别可能导致大量的超时现象和锁竞争。这四种隔离级别采取不同的锁类型来实现若读取的是同一个数据的话就容易发生问题。例如脏读(Drity Read)某个事务已更新一份数据另一个事务在此时读取了同一份数据由于某些原因前一个RollBack了操作则后一个事务所读取的数据就会是不正确的。不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致这可能是两次查询过程中间插入了一个事务更新的原有的数据。幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致例如有一个事务查询了几列(Row)数据而另一个事务却在此时插入了新的几列数据先前的事务在接下来的查询中就会发现有几列数据是它先前所没有的。事务传播级别数据库事务传播级别指的是事务嵌套时应该采用什么策略即在一个事务中调用别的事务该怎么办假如有一下两个事务ServiceA {voidmethodA(){ServiceB.methodB();}}ServiceB {voidmethodB(){}}1PROPAGATION_REQUIRED加入当前正要执行的事务不在另外一个事务里那么就起一个新的事务比如说ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA的时候ServiceA.methodA已经起了事务这时调用ServiceB.methodBServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中他就会为自己分配一个事务。这样在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常事务都会被回滚。即使ServiceB.methodB的事务已经被提交但是ServiceA.methodA在接下来fail要回滚ServiceB.methodB也要回滚2PROPAGATION_SUPPORTS如果当前在事务中即以事务的形式运行如果当前不再一个事务中那么就以非事务的形式运行3PROPAGATION_MANDATORY必须在一个事务中运行。也就是说他只能被一个父事务调用。否则他就要抛出异常4PROPAGATION_REQUIRES_NEW这个就比较绕口了。比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIREDServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW那么当执行到ServiceB.methodB的时候ServiceA.methodA所在的事务就会挂起ServiceB.methodB会起一个新的事务等待ServiceB.methodB的事务完成以后他才继续执行。他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务那么就是存在两个不同的事务。如果ServiceB.methodB已经提交那么ServiceA.methodA失败回滚ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚如果他抛出的异常被ServiceA.methodA捕获ServiceA.methodA事务仍然可能提交。5PROPAGATION_NOT_SUPPORTED当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED那么当执行到ServiceB.methodB时ServiceA.methodA的事务挂起而他以非事务的状态运行完再继续ServiceA.methodA的事务。6PROPAGATION_NEVER不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED而ServiceB.methodB的事务级别是PROPAGATION_NEVER那么ServiceB.methodB就要抛出异常了。7PROPAGATION_NESTED理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是PROPAGATION_REQUIRES_NEW另起一个事务将会与他的父事务相互独立而Nested的事务和他的父事务是相依的他的提交是要等和他的父事务一块提交的。也就是说如果父事务最后回滚他也要回滚的。而Nested事务的好处是他有一个savepoint。行级锁如果有两个事务A,B都有read和write操作如果逻辑是如果表中没有记录则插入那么因为read操作并没有加锁AB进行read操作时有可能表中都没有记录那么事务A,B都会进行插入操作表中将会有两条记录。如果要保证在事务并发时每条事务读取到的数据都是最新的那么只能采用锁。在select语句后加上FOR UPDATE再测试重复提交的问题被解决了。但是问题又来了如果在select语句后加上LOCK IN SHARE MODE那么会报死锁的错误。查看mysql文档SELECT ... LOCK IN SHARE MODEsets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.SELECT ... FORUPDATEsets an exclusive lock on the rows read. An exclusive lock prevents other sessions from accessing the rows for reading or writing.LOCK IN SHARE MODE会在读取的行上加共享锁其他session只能读不能修改或删除如果有其他事务修改了记录那么会等待事务提交后再读取。FOR UPDATE在读取行上设置一个排他锁。阻止其他session读取或者写入行数据这样看起来似乎就能解释为什么使用LOCK IN SHARE MODE会产生死锁了假如两个事务A、B都读取同一行记录那么在这一行就加上了共享锁但是A和B事务中都需要修改这一行那么都要等待对方释放共享锁才能进行结果造成了死锁。只能使用for update来防止死锁和重复插入。这就是mysql的两种行级锁的区别。