MySQL 事务处理和连接池优化

作者: uniapp | 来源:发表于2018-05-27 10:05 被阅读9次

1 事务处理

通过事务处理,我们可以控制一组数据库操作,这组操作要么全部成功,要么全部失败;

比如,在 account 表中,记录了账户 a 和账户 b 均有 1000 元,账户 a 想要往账户 b 转入 100 元。通常的流程是两部操作:

//起始
update account set money=money-100 where name='a';
//意外
update account set money=money+100 where name='b';

但是如果这两条 sql 语句执行过程中出现了意外,应该回到起始点。
在 MySQL 中通过 Transaction 来实现事务处理,使用的流程为:

1 Start Transaction 开启事务
2 Rollback 回滚事务, 事务失效;
3 Commit 提交事务, 事务生效;

利用事务处理改造一下开头提出的转账问题后,流程就变成了:

start transaction;
update account set money=money-100 where name='a';
//意外 
rollback;
update account set money=money+100 where name='b';
//成功 
commit;

在 Java 中封装的 JDBC 使用事务处理通过 Connection 来实现,通过方法setAutoCommitcommit 实现事务的开启和提交。还可以通过 setSavepoint 方法设置回滚点,控制事务回滚过程,下面是一个使用的范例,不清楚 JDBCUtils 的童鞋,请参考我的上篇文章 App 开发者使用 JDBC 驱动操纵 MySql 数据库

public void test2(){
        Connection connection = null;
        Statement stmt = null;
        Savepoint sp = null;
        try{
            connection = JDBCUtils.getConnection();
            connection.setAutoCommit(false);
            stmt = connection.createStatement();
            stmt.executeUpdate("update account set money=money-100 where name='aaa';");
            stmt.executeUpdate("update account set money=money+100 where name='bbb';");

            sp = connection.setSavepoint();

            stmt.executeUpdate("update account set money=money-100 where name='bbb';");
            stmt.executeUpdate("update account set money=money+100 where name='ccc';");
            int i = 1/0;
            connection.commit();
        }catch (Exception e){
            e.printStackTrace();
            try {
                connection.rollback(sp);
                connection.commit();//回滚点前的操作要生效;

            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally {
            try {
                connection.commit();//回滚点前的操作要生效;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JDBCUtils.release(null, stmt, connection);
        }
    }

2 数据库事务的特性

数据库事务特性可以概括为 ACID:

1 Atomicity:原子性;
2 Consistency: 一致性;
3 Isolation: 隔离性;
4 Durable: 持久性;

其中隔离性在多事务操作中显得比较重要,如果隔离性处理不好,可能会造成如下错误:

1 脏读(dirty read): 一个事务读到了其他事务未提交的数据;
2 不可重复读(unreatable read): 一个事务在操作时,读到其他事务已经提交的数据, 主要是 update 语句更新;
3 虚读(phantom read):  一个事务在操作时,读到其他事务已经提交的数据;主要是 insert 语句插入记录;

MySQL 数据库中设置了如下 4 个类型的隔离性级别,用来解决不同的多事务处理场景:

1 read uncommitted (读未提交) 会产生脏读,不重复读,虚度可能发生;
2 read commited (读已提交) 不会脏读,会引发不可重复读,虚读可能发生;
3 repeatable read (可以重复读) 不会脏读,不可重复读,虚度有可能;
4 serializable(串行化) 不会发生三类问题;事务有先后顺序;

MySQL 设置事务处理隔离级别的命令是:set session transaction isolation level 隔离值;,默认的隔离级别是 repeatable read
3 数据库连接池优化程序性能

多事务处理中,需要反复连接,释放对数据库的操作。当数量众多时会导致数据库服务器内存溢出、宕机。Sun 公司给出的解决方案是采用连接池优化对数据库进行访问,其大概原理是:一次性创造多个连接对象放到连接池中,当需要操作数据库时,就从连接池取用,避免了不断创建和释放连接的过程,进而显著提高程序性能。

模拟一个数据库连接池如下:

public class MyDataSource implements DataSource {

    //增删操作,使用 LinkedList 效率会更高;
    private java.util.LinkedList<Connection> pool = new LinkedList<Connection>();

    public MyDataSource(){
        //创建连接池
        System.out.println("创建 10 个连接池");
        for (int i = 0; i < 10; i++) {
            Connection connection = JDBCUtils.getConnection();
            pool.add(connection);
        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        //TODO
        if (pool.isEmpty()){
            for (int i = 0;i < 3;i++){
                Connection connection = JDBCUtils.getConnection();
                pool.add(connection);
            }
        }
        final Connection conn = pool.removeFirst();
        //代理对象
        Connection proxyConn = (Connection)Proxy.newProxyInstance(MyDataSource.class.getClassLoader(),
                conn.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        if (method.getName().equals("close")){
                            //close 放回池子

                            addBack2Pool(conn);
                            return null;
                        }
                        return method.invoke(conn, args);
                    }
                });
        return conn;
    }

    public void addBack2Pool(Connection conn){
        System.out.println("放回");
        pool.add(conn);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

使用 MyDatabase:

public class MyDataSourceTest {
    @Test
    public void test1(){
        Connection connection = null;
        PreparedStatement stmt =null;
        MyDataSource ds = null;
        try {
            ds = new MyDataSource();
            connection = ds.getConnection();
            stmt = connection.prepareStatement("update" +
                    "account set money=? where name=?;");
            stmt.setDouble(1, 99999);
            stmt.setString(2, "aaa");
            stmt.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            ds.addBack2Pool(connection);
            //对close方法进行加强;
            JDBCUtils.release(null, stmt , connection);
        }
    }
}

在创建 Connection 时,利用代理重写了 connection 的 close 方法,这样在使用 JDBCUtils 对 connection 释放时,能主动将其重放回重用池。
Java 最好用的一点就是有各种成型的 API ,重用池也是。Steve Waldman 维护的开源框架 c3p0 就是非常好用的重用池,使用很方便,先导入 jar 包,使用示例如下:

public void test1(){
        Connection conn = null;
        PreparedStatement stmt = null;
        try{
            ComboPooledDataSource cpds = new ComboPooledDataSource();
            cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
            cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/数据库名" );
            cpds.setUser("root");
            cpds.setPassword("11111111");

            conn = cpds.getConnection();
            stmt = conn.prepareStatement("update " +
                    "account set money=? where name=?;");
            stmt.setDouble(1, 888888);
            stmt.setString(2, "a");
            stmt.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //对 close 方法进行了加强;
            JDBCUtils.release(null, stmt, conn);
        }
    }

c3po 还能通过 xml 文件和 properties 文件实现预定义配置,感觉超级人性,有木有?更多实现请参看它的 git

喜欢和关注都是对我的鼓励和支持~

相关文章

  • MySQL 事务处理和连接池优化

    1 事务处理 通过事务处理,我们可以控制一组数据库操作,这组操作要么全部成功,要么全部失败; 比如,在 accou...

  • SQL优化

    MySQL优化 体系结构 MySQL Server由以下组成:Connection Pool(连接池组件);Man...

  • mysql的概念以及mysql架构分析

    mysql逻辑结构从图中可以看到MySQL由以下几个部分组成连接池管理服务和工具组件SQL接口查询分析器优化器缓存...

  • MySQL 体系结构及存储引擎

    MySQL 体系结构 连接池组件 管理服务和工具组件 SQL接口组件 查询分析器组件 优化器组件 缓冲( Cach...

  • Mysql的架构-1

    mysql的组成部分 连接池组件 管理服务和工具组件 sql接口组件 查询分析器组件 优化器组件 缓冲组件 插件式...

  • MySQL优化

    MySQL架构 网络连接层 服务层连接池系统管理和控制SQL接口解析器, 对SQL语句检查,生成解析树查询优化器缓...

  • 业务系统是如何与mysql进行连接的

    1.我们的业务系统是如何和mysql进行连接的 2.为什么业务系统需要连接池 3.mysql为什么需要连接池

  • MySQL---数据库从入门走向大神系列(六)-事务处理与事务隔

    MySQL 事务处理 简单介绍事务处理: MySQL 事务主要用于处理操作量大,复杂度高的数据。 比如说,在人员管...

  • mysql性能优化-慢查询分析、优化索引和配置

    mysql性能优化-慢查询分析、优化索引和配置 分类:Mysql/postgreSQL 目录 一、优化概述 二、查...

  • MySQL优化

    概述 MySQL优化分为三部分优化: MySQL服务器和配置优化 数据库设计和结构优化 查询优化(重点) MySQ...

网友评论

    本文标题:MySQL 事务处理和连接池优化

    本文链接:https://www.haomeiwen.com/subject/uqcmjftx.html