实战分析:事务的隔离级别和传播属性

什么是事务?

要么全部都要执行,要么就都不执行。

事务所具有的四种特性

原子性,一致性,隔离性,持久性

原子性

个人理解,就是事务执行不可分割,要么全部完成,要么全部拉倒不干。

一致性

关于一致性这个概念我们来举个例子说明吧,假设张三给李四转了100元,那么需要先从张三那边扣除100,然后李四那边增加100,这个转账的过程对于其他事务而言是无法看到的,这种状态始终都在保持一致,这个过程我们称之为一致性。

隔离性

并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据是独立的;

持久性

一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

为什么会出现事务的隔离级别?

我们都知道,数据库都是有相应的事物隔离级别。之所以需要分成不同级别的事务,这个是因为在并发的场景下,读取数据可能会有出现脏读,不可重复读以及幻读的情况,因此需要设置相应的事物隔离级别。

为了方便理解,我们将使用java程序代码来演示并发读取数据时候会产生的相应场景:

环境准备:

  • jdk8
  • mysql数据

建立测试使用表:

CREATE TABLE `money` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `money` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

一个方便于操作mysql的简单JdbcUtil工具类:

import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

/**
 * Jdbc操作数据库工具类
 *
 * @author idea
 * @version 1.0
 */
public class JdbcUtil {

    public static final String DRIVER;
    public static final String URL;
    public static final String USERNAME;
    public static final String PASSWORD;

    private static Properties prop = null;

    private static PreparedStatement ps = null;

    /**
     * 加载配置文件中的信息
     */
    static {
        prop = new Properties();
        try {
            prop.load(JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER = prop.getProperty("driver");
        URL = prop.getProperty("url");
        USERNAME = prop.getProperty("username");
        PASSWORD = prop.getProperty("password");
    }

    /**
     * 获取连接
     *
     * @return void
     * @author blindeagle
     */
    public static Connection getConnection() {
        try {
            Class.forName(DRIVER);
            Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return conn;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 数据转换为list类型
     *
     * @param rs
     * @return
     * @throws SQLException
     */
    public static List convertList(ResultSet rs) throws SQLException {
        List list = new ArrayList();
        //获取键名
        ResultSetMetaData md = rs.getMetaData();
        //获取行的数量
        int columnCount = md.getColumnCount();
        while (rs.next()) {
            //声明Map
            HashMap<String,Object> rowData = new HashMap();
            for (int i = 1; i <= columnCount; i++) {
                //获取键名及值
                rowData.put(md.getColumnName(i), rs.getObject(i));
            }
            list.add(rowData);
        }
        return list;
    }
}

脏读

所谓的脏读是指读取到没有提交的数据信息。

模拟场景:两个线程a,b同时访问数据库进行操作,a线程需要插入数据到库里面,但是没有提交事务,这个时候b线程需要读取数据库的信息,将a里面所要插入的数据(但是没有提交)给读取了进来,造成了脏读现象。

代码如下所示:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @author idea
 * @date 2019/7/2
 * @Version V1.0
 */
public class DirtyReadDemo {

    public static final String READ_SQL = "SELECT * FROM money";
    public static final String WRITE_SQL = "INSERT INTO `money` (`id`, `money`) VALUES (‘3‘, ‘350‘)";

    public Object lock = new Object();

    /**
     * 脏读模拟(注意:需要设置表的存储引擎为innodb类型)
     */
    public static void dirtyRead() {
        try {
            Connection conn = JdbcUtil.getConnection();
            conn.setAutoCommit(false);
            PreparedStatement writePs = conn.prepareStatement(WRITE_SQL);
            writePs.executeUpdate();
            System.out.println("执行写取数据操作----");

            Thread.sleep(500);

            //需要保证连接不同
            Connection readConn = JdbcUtil.getConnection();
            //注意这里面需要保证提交的事物等级为:未提交读
            readConn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            PreparedStatement readPs = readConn.prepareStatement(READ_SQL);
            ResultSet rs = readPs.executeQuery();
            System.out.println("执行读取数据操作----");
            List list = JdbcUtil.convertList(rs);
            for (Object o : list) {
                System.out.println(o);
            }
            readConn.close();

        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        dirtyRead();
    }
}

由于这个案例里面的事物隔离级别知识设置在了TRANSACTION_READ_UNCOMMITTED层级,因此对于没有提交事务的数据也会被读取进来。造成了脏数据读取的情况。

因此程序运行之后的结果如下:

为了预防脏读的情况发生,我们通常需要提升事务的隔离级别,从原先的TRANSACTION_READ_UNCOMMITTED提升到TRANSACTION_READ_COMMITTED,这个时候我们再来运行一下程序,会发现原先有的脏数据读取消失了:

不可重复读

所谓的不可重复读,我的理解是,多个线程a,b同时读取数据库里面的数据,a线程负责插入数据,b线程负责写入数据,b线程里面有两次读取数据库的操作,分别是select1和select2,由于事务的隔离级别设置在了TRANSACTION_READ_COMMITTED,所以当select1执行了之后,a线程插入了新的数据,再去执行select2操作的时候会读取出新的数据信息,导致出现了不可重复读问题。

演示代码:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * 不可重复读案例
 * @author idea
 * @date 2019/7/2
 * @Version V1.0
 */
public class NotRepeatReadDemo {

    public static final String READ_SQL = "SELECT * FROM money";
    public static final String WRITE_SQL = "INSERT INTO `money` (`id`, `money`) VALUES (‘3‘, ‘350‘)";

    public Object lock = new Object();

    /**
     * 不可重复读模拟
     */
    public  void notRepeatRead() {
        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try (Connection conn = JdbcUtil.getConnection();) {
                    //堵塞等待唤醒
                    synchronized (lock) {
                        lock.wait();
                    }
                    conn.setAutoCommit(true);
                    PreparedStatement ps = conn.prepareStatement(WRITE_SQL);
                    ps.executeUpdate();
                    System.out.println("执行写取数据操作----");
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread readThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Connection readConn = JdbcUtil.getConnection();
                    readConn.setAutoCommit(false);
                    readConn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    PreparedStatement readPs = readConn.prepareStatement(READ_SQL);
                    ResultSet rs = readPs.executeQuery();
                    System.out.println("执行读取数据操作1----");
                    List list = JdbcUtil.convertList(rs);
                    for (Object obj : list) {
                        System.out.println(obj);
                    }

                    synchronized (lock){
                        lock.notify();
                    }

                    Thread.sleep(1000);
                    ResultSet rs2 = readPs.executeQuery();
                    System.out.println("执行读取数据操作2----");
                    List list2 = JdbcUtil.convertList(rs2);
                    for (Object obj : list2) {
                        System.out.println(obj);
                    }
                    readConn.commit();
                    readConn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        writeThread.start();
        readThread.start();
    }

    public static void main(String[] args) {
        NotRepeatReadDemo notRepeatReadDemo=new NotRepeatReadDemo();
        notRepeatReadDemo.notRepeatRead();
    }

}

在设置了TRANSACTION_READ_COMMITTED隔离级别的情况下,上述程序的运行结果为:

为了避免这种情况的发生,需要保证在同一个事务里面,多次重复读取的数据都是一致的,因此需要将事务的隔离级别从TRANSACTION_READ_COMMITTED提升到TRANSACTION_REPEATABLE_READ级别,这种情况下,上述程序的运行结果为:

幻读

官方文档对于幻读的定义如下:

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

读到上一次没有返回的记录,看起来是幻影一般。

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。为了解决这种情况,可以选择将事务的隔离级别提升到TRANSACTION_SERIALIZABLE。

什么是TRANSACTION_SERIALIZABLE?

TRANSACTION_SERIALIZABLE是当前事务隔离级别中最高等级的设置,可以完全服从ACID的规则,通过加入行锁的方式(innodb存储引擎中)来防止出现数据并发导致的数据不一致性问题。为了方便理解,可以看看下方的程序:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.concurrent.CountDownLatch;

/**
 * @author idea
 * @date 2019/7/2
 * @Version V1.0
 */
public class FantasyReadDemo {

    public static final String READ_SQL = "SELECT * FROM money";
    public static final String UPDATE_SQL = "UPDATE `money` SET `money` = ? WHERE `id` = 3;n";

    public CountDownLatch countDownLatch=new CountDownLatch(2);

    public void readAndUpdate1() {
        try (Connection conn = JdbcUtil.getConnection();) {
            conn.setAutoCommit(false);
            PreparedStatement ps = conn.prepareStatement(READ_SQL);
            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
            ResultSet rs = ps.executeQuery();
            rs.next();
            int currentMoney = (int) rs.getObject(2);
            System.out.println("执行写取数据操作----" + currentMoney);
            //堵塞等待唤醒
            countDownLatch.countDown();
            PreparedStatement writePs = conn.prepareStatement(UPDATE_SQL);
            writePs.setInt(1, currentMoney - 1);
            writePs.execute();
            conn.commit();
            writePs.close();
            ps.close();
            System.out.println("执行写操作结束---1");
        } catch (Exception e) {
            e.printStackTrace();
            readAndUpdate1();
        }
    }

    public void readAndUpdate2() {
        try (Connection conn = JdbcUtil.getConnection();) {
            conn.setAutoCommit(false);
            PreparedStatement ps = conn.prepareStatement(READ_SQL);
            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
            ResultSet rs = ps.executeQuery();
            rs.next();
            int currentMoney = (int) rs.getObject(2);
            System.out.println("执行写取数据操作----" + currentMoney);
            //堵塞唤醒
            countDownLatch.countDown();
            PreparedStatement writePs = conn.prepareStatement(UPDATE_SQL);
            writePs.setInt(1, currentMoney - 1);
            writePs.execute();
            conn.commit();
            writePs.close();
            ps.close();
            System.out.println("执行写操作结束---2");
        } catch (Exception e) {
            //使用串行化事务级别能够较好的保证数据的一致性,可串行化事务 serializable 是事务的最高级别,在每个读数据上加上锁
            //innodb里面是加入了行锁,因此出现了异常的时候,只需要重新执行一遍事务即可。
            e.printStackTrace();
            readAndUpdate2();
        }
    }

    public void fantasyRead() {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndUpdate1();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndUpdate2();
            }
        });
        try {
            thread1.start();
//            Thread.sleep(500);
            thread2.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        FantasyReadDemo fantasyReadDemo = new FantasyReadDemo();
        fantasyReadDemo.fantasyRead();
    }

}

这里面将事务的隔离级别设置到了TRANSACTION_SERIALIZABLE,但是在运行过程中为了保证数据的一致性,串行化级别的事物会给相应的行数据加入行锁,因此在执行的过程中会抛出下面的相关异常:

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:377)
    .......

这里为了方便演示,在抛出异常的时候重新再次执行了一遍事务的方法,从而完成多次事务并发执行。

但是实际应用场景中,我们对于这种并发状态造成的问题都会交给业务层面加入锁来解决冲突,因此TRANSACTION_SERIALIZABLE隔离级别一般在应用场景中比较少见。

七种事务的传播机制

事务的七种传播机制分别为:

REQUIRED(默认) 默认的事务传播机制,如果当前不支持事务,那么就创建一个新的事务。

SUPPORTS 表示支持当前的事务,如果当前没有事务,则不会单独创建事务

以上的这两种事务传播机制比较好理解,接下来的几种事务传播机制就比上边的这几类稍微复杂一些了。

REQUIRES_NEW

定义: 创建一个新事务,如果当前事务已经存在,把当前事务挂起。
为了更好的理解REQUIRES_NEW的含义,我们通过下边的这个实例来进一步理解:

有这么一个业务场景,需要往数据插入一个account账户信息,然后同时再插入一条userAccount的流水信息。(只是模拟场景,所以对象的命名有点简陋)
直接来看代码实现,内容如下所示:

/**
 * @author idea
 * @data 2019/7/6
 */
@Service
public class AccountService {

    @Autowired
    private AccountDao accountDao;
    @Autowired
    private UserAccountService userAccountService;

    /**
     * 外层定义事务, userAccountService.saveOne单独定义事务
     *
     * @param accountId
     * @param money
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void saveOne(Integer accountId, Double money) {
        accountDao.insert(new Account(accountId, money));
        userAccountService.saveOne("idea", 1001);
        //这里模拟抛出异常
        int j=1/0;
    }
}

再来看userAccountService.saveOne函数:

/**
 * @author idea
 * @data 2019/7/6
 */
@Service
public class UserAccountService {

    @Autowired
    private UserAccountDao userAccountDao;

    /**
     * @param username
     * @param accountId
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveOne(String username,Integer accountId){
        userAccountDao.insert(new UserAccount(username,accountId));
    }
}

执行程序的时候,AccountService.saveOne里面的 userAccountService.saveOne函数为单独定义的一个事务,而且传播属性为REQUIRES_NEW。因此在执行外层函数的时候,即使后边抛出了异常,也并不会影响到内部 userAccountService.saveOne的函数执行。

REQUIRES_NEW 总是新启一个事务,这个传播机制适用于不受父方法事物影响的操作,比如某些业务场景下需要记录业务日志,用于异步反查,那么不管主体业务逻辑是否完成,日志都需要记录下来,不能因为主体业务逻辑报错而丢失日志;但是本身是一个单独的事物,会受到回滚的影响,也就是说 userAccountService.saveOne里面要是抛了异常,子事务内容一起回滚。

NOT_SUPPORTED

定义:无事务执行,如果当前事务不存在,把已存在的当前事务挂起。

还是接上边的代码来进行试验:

账户的转账操作:

userAccountService内部的saveOne操作:

在执行的过程中,userAccountService.saveOne抛出了异常,但是由于该方法申明的事物传播属性为NOT_SUPPORTED级别,因此当子事务内部抛出异常的时候,子事务本身不会回滚,而且也不会影响父类事务的执行。

 

NOT_SUPPORTED可以用于发送提示消息,站内信、短信、邮件提示等。不属于并且不应当影响主体业务逻辑,即使发送失败也不应该对主体业务逻辑回滚,并且执行过程中,如果父事务出现了异常,进行回滚,也不会影响子类的事务

NESTED

定义:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。

关于Nested的定义,我个人感觉网上写的比较含糊,所以自己通过搭建Demo来强化理解,还是原来的例子,假设说父类事务执行的过程中抛出了异常如下,那么子类也要跟着回滚:


当父事务出现了异常之后,进行回滚,子事务也会被牵扯进来一起回滚。

MANDATORY

定义:MANDATORY单词中文翻译为强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。

这个比较好理解

当子方法定义了事务,且事务的传播属性为MANDATORY级别的时候,如果父方法没有定义事务操作的话,就会抛出异常。(此时的子方法会将数据记录到数据库里面)

NEVER

定义:当前如果存在事务则抛出异常

在执行userAccountService.saveOne函数的时候,发现父类的方法定义了事务,因此会抛出异常信息,并且userAccountService.saveOne会回滚。

传播属性小结:

PROPAGATION_NOT_SUPPORTED
不会受到父类事务影响而回滚,自己也不会影响父类函数,出现异常后会自动回滚。

PROPAGATION_REQUIRES_NEW 
不会受到父类事务影响而回滚,自己也不会影响父类函数,出现异常后会自动回滚。

NESTED
会受到父类事务影响而回滚,出现异常后自身也回滚。如果不希望影响父类函数,那么可以通过使用try catch来控制操作。

MANDATORY
强制使用当期的事物,如果当前的父类方法没有事务,那么在处理数据的时候就会抛出异常

NEVER
当前如果存在事务则抛出异常

REQUIRED(默认) 默认的事务传播机制,如果当前不支持事务,那么就创建一个新的事务。

SUPPORTS 表示支持当前的事务,如果当前没有事务,则不会单独创建事务

本文的全部相关代码都已经上传到gitee上边了,欢迎感兴趣的朋友前往进行代码下载:

https://gitee.com/IdeaHome_admin/wfw

原文地址:https://www.cnblogs.com/javazhiyin/p/11150482.html

时间: 2024-10-11 12:21:12

实战分析:事务的隔离级别和传播属性的相关文章

事物的隔离级别和传播属性

1.ACID:原子性.一致性.隔离性.持久性 2.事务:显示,隐士事物 3.不一致性: 更新丢失(覆盖了).脏读(读取到了未提交的数据).不可重复读(两次读取的结果不一致) 4. 四个隔离级别:--主要保证查询时最新的 READ UNCOMMITTED --不申请X锁与S锁 READ COMMITTED     --申请S锁 REPEATABLE READ    --锁定查询结果,这样其他事务不可以更新查询到的结果数据 SERIALIZABLE           --数据集上放置一个范围锁 七

事务的隔离级别和传播行为

一.事务的隔离级别 1.五种事务的隔离级别 ①读_未提交(read_uncommitted): 会出现脏读.不可重复读.幻读.(隔离级别最低,并发性能高) ②读_已提交(read_committed):会出现不可重复读.幻读.(锁定正在读取的行) ③重复读(repeatable_read):会出现幻读.(锁定所读取的所有行) ④序列化(serializable):保证所有的情况不会发生:(锁表) ⑤默认的隔离级别: 大多数的默认隔离级别是:读已提交(read_commited)如:Sql Ser

事务的隔离级别和传播特性

脏读:当一个事务读取另一个事务未提交的内容,之后由于另一个事务出现了异常回滚了事务,结果造成读取的数据不一致 不可重复读:指一个事务多次读取同一数据,而另一个事务多次对数据进行了修改的操作,这样就导致了第一个事务每次读取的数据不一样, 幻读:当事务一对整张表的数据进行操作时对其进行了新增行,而另一个事务对其进行了删除行,而这时事务一本身对其进行了新增行然而发现并没有对其新增行而产生了幻觉 脏读与不可重复读的区别:脏读是对没有提交的数据进行查询,不可重复读是对已经提交的数据进行的查询 不可重复读与

分析事务的隔离级别

在DBMS中,事务保证了一个操作序列可以全部执行或者全部不执行(原子性),从一个状态撞边到另外一个状态(一致性).由于事务满足持久性.所以一旦事务被提交之后,数据就能被持久化下来,又因为事务是满足隔离性的,所以,当多个事务同时处理同一个数据的时候,多个事务是互不影响的.所以,在多个事务并发操作的过程中,如果控制不好隔离级别,就可能产生脏读,不可重复读或者幻读等现象 在数据库事务的ACID四个属性中,隔离性是最放松的一个.可以在数据操作过程中利用数据库的锁机制或者多版本并发控制机制获取更高的隔离等

Spring事务隔离级别和传播特性

在Spring中,声明式事务是用事务参数来定义的.一个事务参数就是对事务策略应该如何应用到某个方法的一段描述,如下图所示一个事务参数共有5个方面组成: 传播行为 事务的第一个方面是传播行为.传播行为定义关于客户端和被调用方法的事务边界.Spring定义了7中传播行为. 传播行为 意义 PROPAGATION_MANDATORY 表示该方法必须运行在一个事务中.如果当前没有事务正在发生,将抛出一个异常 PROPAGATION_NESTED 表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌

数据库事务隔离级别及传播行为

一.隔离级别: 数据库事务的隔离级别有4个,由低到高依次为Read uncommitted.Read committed.Repeatable read.Serializable,这四个级别可以逐个解决脏读.不可重复读.幻读这几类问题. 1. ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据.      这种隔离级别会产生脏读,不可重复读和幻像读.2. ISOLATION_READ_COMMITTED:保证一个事务修改的

数据库事务的四大特性以及事务的隔离级别-与-Spring事务传播机制&amp;隔离级别

本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响. ⑵ 一致性(Consistency) 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执

事务隔离级别、传播行为及锁机制

1.事务的特性 事务具备以下四个特性,简称ACID属性. 原子性(Atomicity): 事务是一个完整的操作,事务的各步操作都是不可再分的,要么都执行, 要么都不执行. 一致性(Consistency): 当事务完成时,数据必须处于一致的状态. 隔离性(Isolation): 并发事务之间相互独立.隔离,它不应以任何方式依赖于或影响其他事 务. 持久性(Durability): 事务完成后,它对数据库的修改被永久保持. 2. 事务的隔离级别 ① Read uncommitted (读未提交):

数据库事务【隔离级别】

为了快速同步数据的需要,我分段执行了两次python脚本,即开启了两个进程同步数据,结果服务器不时报出数据库死锁异常,通过排查代码和数据库日志发现,是由长事务并发引起的.代码中有入账和出账两个方法,里面涉及操作较多,都为其加了事务,抛出异常时可自动回滚,采用数据库(mysql)默认的隔离级别(Repeatable read).提到并发,一般就会想到用同步代码块的方法的处理,但是由于项目是分布式的,共用一个主库,单单在代码加锁是不能保证数据的准确的,那就只能在数据库层面去考虑加锁了.由于数据量暂时