MySQL AutoCommit带来的问题
原创内容,转载请注明出处
http://www.cnblogs.com/wingsless/p/6803542.html
现象描述
测试中发现,服务A在得到了服务B的注册用户成功response以后,开始调用查询用户信息接口,却发现无法查询出任何结果。检查binlog发现,在查询请求之前,数据库确实已经完成了commit操作,并且可以在sqlyog等客户端工具中查询出正确的结果。
下面是这个流程的时序图:

问题出现在Server A向数据库发起查询的时候,返回的结果总是空。
问题分析
这个问题显然是一个事务隔离的问题,最开始的思路是,服务A所在的机器,其事务开启时间应该是在服务B的机器commit操作之前开启的,但是通过DEBUG日志分析connection的获取和提交时间,发现两个服务器之间不存在这样的关系,服务B永远是在服务A返回了正确的response之后才会调用数据库接口,进行getConnection操作,进而进行查询操作。
显然这并不能支持刚才的设想,但是结论一定是正确的,就是因为事务隔离级别导致了Server A读到的永远是快照,发生了可重复读。
后来调整了一下思路,发现MySQL还有一个特性就是AutoCommit,即默认情况下,MySQL是开启事务的,下面表格能说明问题,表1:

但是,如果AutoCommit不是默认开启呢?结果就会变成下面的表格,表2:

在关闭AutoCommit的条件下,SessionA在T1和T2两个时间点执行的SQL语句其实在一个事务里,因此每次读到的其实只是一个快照。
那么在连接池条件下,情况如何?
设置一个极端条件,连接池只给一个连接,编写两个类,一个负责插入数据,一个负责循环读取数据,但是读取数据的类在执行读取方法之前,会执行一个空方法,这个方法只会做一件事情,就是获取连接,将其AutoCommit设置为FALSE,关闭连接。
两段代码如下:
写入线程:
public static void main( String[] args ) throws Exception
{
DBconfigEntity entity = new DBconfigEntity();
entity.setDbName("test");
entity.setDbPasswd("123456");
entity.setDbUser("root");
entity.setIp("127.0.0.1");
entity.setPort(3306);
MysqlClient.init(entity);
MysqlClient instance = MysqlClient.getInstance();
Connection conn = instance.getConnection();
conn.setAutoCommit(false);
String sql = "insert into test1(uname) values (?)";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setString(1, "PPP");
statement.executeUpdate();
conn.commit();
statement.close();
conn.close();
//永远休眠,但是永远持有连接池
Thread.sleep(Long.MAX_VALUE);
}
读取类:
public class GetClient {
private void query() throws SQLException
{
System.out.println("start");
MysqlClient instance = MysqlClient.getInstance();
Connection conn = instance.getConnection();
String sql = "select uname from test1";
PreparedStatement statement = conn.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("uname"));
}
statement.close();
rs.close();
conn.close();
}
private void nothing() throws SQLException
{
MysqlClient instance = MysqlClient.getInstance();
Connection conn = instance.getConnection();
conn.setAutoCommit(false);
conn.close();
}
public static void main(String[] args) throws SQLException, InterruptedException, ClassNotFoundException {
DBconfigEntity entity = new DBconfigEntity();
entity.setDbName("test");
entity.setDbPasswd("123456");
entity.setDbUser("root");
entity.setIp("127.0.0.1");
entity.setPort(3306);
MysqlClient.init(entity);
GetClient client = new GetClient();
client.nothing();
while (true) {
client.query();
Thread.sleep(5000);
}
}
}
表初始没有任何数据,首先运行读取类,此时读取类只会不停的打印“start”,此时启动写入类,观察发现,console并不会打印数据库test1表查询的结果,但是在数据库工具中查看,test1表确实已经有了数据。
这是因为在连接池条件下,如果这个连接之前被借出过,并且曾经被设置成了AutoCommit为FALSE,那么这个连接在其生存时间内,永远会默认开启事务,这是MySQL自身决定的,因为连接池只是持有连接,代码中的close操作只是将该连接还给连接池,但是并没有真的将连接销毁,因此连接的属性仍然保持上次设置的样子。当另一个方法开始,重新执行getConnection获取链接时,是有可能获取到之前被设置为AutoCommit为FALSE的连接的,这个时候就相当于上面的表2中Session A在T3时间点的情况,无论如何查询,都会查不出任何数据来。
如下图:

无论如何commit,都无法改变这个连接的autocommit属性。
因为测试时采用的是一个连接这种极端条件,因此该现象非常容易复现,且是100%的复现,但是在测试条件下,并非100%复现,而是在重启之后会好一段时间,一段时间以后就会重新出现这个情况。
如果将读取类的代码稍加修改:
public class GetClient {
private void query() throws SQLException
{
System.out.println("start");
MysqlClient instance = MysqlClient.getInstance();
Connection conn = instance.getConnection();
conn.setAutoCommit(true);
String sql = "select uname from test1";
PreparedStatement statement = conn.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("uname"));
}
statement.close();
rs.close();
conn.close();
}
private void nothing() throws SQLException
{
MysqlClient instance = MysqlClient.getInstance();
Connection conn = instance.getConnection();
conn.setAutoCommit(false);
conn.close();
}
public static void main(String[] args) throws SQLException, InterruptedException, ClassNotFoundException {
DBconfigEntity entity = new DBconfigEntity();
entity.setDbName("test");
entity.setDbPasswd("123456");
entity.setDbUser("root");
entity.setIp("127.0.0.1");
entity.setPort(3306);
MysqlClient.init(entity);
GetClient client = new GetClient();
client.nothing();
while (true) {
client.query();
Thread.sleep(5000);
}
}
}
注意我在query方法中加入这一句:conn.setAutoCommit(true);
此时这个问题不再出现。
源码分析
jdbc驱动源码分析
Connection是Java提供的一个标准接口:java.sql.Connection,其具体实现是:com.mysql.jdbc.ConnectionImpl。
分析jdbc驱动代码可知,jdbc默认的AutoCommit状态是TRUE:

这实际上和MySQL的默认值是一样的。
tomcat-jdbc源码分析
tomcat-jdbc的close方法由拦截器实现,具体的逻辑代码:
if (compare(CLOSE_VAL,method)) {
if (connection==null) return null; //noop for already closed.
PooledConnection poolc = this.connection;
this.connection = null;
pool.returnConnection(poolc);
return null;
}
实际上此处只是将连接还给了连接池,没有对连接进行任何处理。
tomcat-jdbc维护了两个Queue:busy和idle,用于存放空闲和已借出连接,连接还给连接池的过程简单的说就是将该连接从busy队列中移除,并放在idle队列中的过程。
boneCP源码分析
根据实际使用的经验看,boneCP连接池在使用的过程中并没有出现这个问题,分析boneCP的Connection具体实现,发现在close方法的具体实现中,有这样的一段代码逻辑:
if (!getAutoCommit()) {
setAutoCommit(true);
}
这段逻辑会判断该连接的AutoCommit属性是否为FALSE,如果是,就自动将其置为TRUE。
因此,在这个连接被交还回连接池时,AutoCommit属性总是TRUE。
结论
任何查询接口都应该在获取连接以后进行AutoCommit的设置,将其设置为true。
原创内容,转载请注明出处
http://www.cnblogs.com/wingsless/p/6803542.html
MySQL AutoCommit带来的问题的更多相关文章
- MySQL autocommit 和 start transaction
autocommit 和 start transaction 都是事务相关的命令.类似MyISAM的mysql引擎就不支持. autocommit 默认是ON状态,即sql语句是自动提交的 show ...
- mysql autocommit
当autocommit为开启状态时,即使没有手动start transaction开启事务,mysql默认也会将用户的操作当做事务即时提交(自动帮我们 db.commit()) autocommit开 ...
- mysql autocommit=OFF导致wordpress 建立数据库连接时出错
今天安装wordpress完成后跳转到login页面时,出现建立数据库连接时出错.网上清一色的拷贝http://mt.sohu.com/20160917/n468547634.shtml的答案. 只能 ...
- MySQL二进制日志总结
二进制日志简单介绍 MySQL的二进制日志(binary log)是一个二进制文件,主要用于记录修改数据或有可能引起数据变更的MySQL语句.二进制日志(binary log)中记录了对MySQL数据 ...
- mysql 5.7 配置文件说明
1.配置文件样例 [client] #password= socket=/data/var/mysql/mysql.sock [mysqld_safe] pid-file=/data/var/mysq ...
- mysql 5.7配置项最详细的解释
配置样例 首先提供一个我使用的配置样例 [client] #password=88888888 socket=/data/var/mysql/mysql.sock [mysqld_safe] pid- ...
- 我的MYSQL学习心得(十七) 复制
我的MYSQL学习心得(十七) 复制 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...
- mysql binlog日志优化及思路
在数据库安装完毕,对于binlog日志参数设置,有一些参数的调整,来满足业务需求或使性能最大化.Mysql日志主要对io性能产生影响,本次主要关注binlog 日志. 查一下二进制日志相关的参数 ...
- MySQL Performance tuning
1.表级锁状态 mysql> show status like 'table%'; +----------------------------+-----------+ | Variable_n ...
随机推荐
- JavaScript当页面关闭时向后台发送请求
今天做项目时遇上一个需求,当浏览器或页面关闭时将数据存储到数据库内.实现思想是采用js监测onunload然后发送请求.结果失败,刷新可以发送但是关闭并不能,整了一整天并没有解决,最后找到了解决办法. ...
- cuda线程/线程块索引小结
内建变量: threadIdx(.x/.y/.z代表几维索引):线程所在block中各个维度上的线程号 blockIdx(.x/.y/.z代表几维索引):块所在grid中各个维度上的块号 blockD ...
- 什么是https
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取.所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议. HTTPS简介 HTTPS其实是有两部分组成:HTTP + SSL ...
- JavaScript对象的深浅复制
前言 从层次上来看,对象的复制可以简单地分为浅复制和深复制,顾名思义,浅复制是指只复制一层对象的属性,不会复制对象中的对象的属性,对象的深复制会复制对象中层层嵌套的对象的属性. 在复制对象时,除了要复 ...
- C语言 动态数组实现
一.概述 C语言是不能直接定义动态数组的,数组必须在初始化时确定长度. 如果要在程序运行时才确定数组的长度,就需要在运行的时候,自己去向系统申请一块内存用动态内存分配实现动态数组. 二.动态内存分配函 ...
- 老李分享:大数据测试中java和hadoop关系
Hadoop的创始人是Doug Cutting, 同时也是著名的基于Java的检索引擎库Apache Lucene的创始人.Hadoop本来是用于著名的开源搜索引擎Apache Nutch,而Nutc ...
- mysql 主从同步 实现增量备份
数据库复制 replication 的实现原理 1:主服务器凡运行语句,都产生一个二进制日志 binlog 2:从服务器不断读取主服务器的binlog 3:从主服务读取到的binlog,转换为自身可执 ...
- 简单分析下用yii2的yii\helpers\Html类和yii.js实现的post请求
yii2提供了很多帮助类,比如Html.Url.Json等,可以很方便的实现一些功能,下面简单说下这个Html.用yii2写view时时经常会用到它,今天在改写一个页面时又用到了它.它比较好用的地方就 ...
- iOS关于JSONKit解析Unicode字符内容出错,问题出在\u0000
JSONKit虽然很强大,但是一些特殊的Unicode,比如\u0000是无法解析的. 在github上作者解释了这个问题,说这个是内容提供的错误,不符合标准的内容,所以他不认为这个是自己的错误,这个 ...
- MySQL索引统计信息更新相关的参数
MySQL统计信息相关的参数: 1. innodb_stats_on_metadata(是否自动更新统计信息),MySQL 5.7中默认为关闭状态 仅在统计信息配置为非持久化的时候生效. 也就是说在i ...