MyBatis中的Cursor 百万级数据的应用
一、简介
MyBatis中的Cursor是一种用于处理大量数据查询的特殊类型。它允许我们在查询结果集中逐行获取数据,而不是一次性将所有数据加载到内存中。这对于处理大型数据集非常有用,可以减少内存消耗并提高查询性能。
二、优点和使用
正对大数据的查询操作时,如果我们一次性,例如将一百万的数据加载到内存中很大可能会导致我们的系统发生内存溢出的错误,这个时候我们就可以考虑使用MyBatis中的Cursor流式查询,Cursor它并不会一次性的将我们在数据库中查询到的数据全部返回过来,而是先将一部分的数据存入到内存里面,然后通过迭代的方式在获取数据,Cursor实现了迭代器iterator接口,我们可以通过next方法获取数据。一般我们在需要查询大量数据进行操作时候就可以使用Cursor提升效率,降低内存溢出的分险。例如我们需要将大量的数据导入execl的时候我们就可以使用Cursor 实现。
三、cursor测试案例
我们可以测试一下先创建一个表test_table
CREATE TABLE test_table (
id INT PRIMARY KEY,
name VARCHAR(20),
age INT,
address VARCHAR(200)
);
像表中插入十万的数据
-- 创建一个存储过程,用于插入10万测试数据
DELIMITER //
CREATE PROCEDURE insert_test_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 100000 DO
-- 随机生成姓名和年龄
SET @name = CONCAT('name', i);
SET @address = CONCAT('address......................', i);
SET @age = FLOOR(RAND() * 100);
-- 插入数据
INSERT INTO test_table (id, name, age, address) VALUES (i, @name, @age, @address);
-- 更新计数器
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
调用存储过程
-- 调用存储过程
CALL insert_test_data();
编写两个测试的方法一个是普通的查询,另一个是流式查询
返回Cursor的接口,插入了10万条数据,经历10次union all之后变成了一百万数据了。
@Select("select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table")
@Options(resultSetType = ResultSetType.FORWARD_ONLY)
Cursor<Person> selectAll();
放回正常list的接口
@Select("select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table union all " +
"select * from mogu_blog.test_table")
List<Person> selectAllList();
编写一个测试程序
public class DemoTest {
private SqlSessionFactory sqlSessionFactory;
/**
*Cursor测试
*/
@Test
public void testMybatisCursor() throws IOException, InterruptedException {
Thread.sleep(20000);
long start = System.currentTimeMillis();
SqlSession sqlSession = openSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Cursor<Person> persons = mapper.selectAll();
int total = 0;
for (Person person : persons) {
total++;
}
long end = System.currentTimeMillis();
System.out.println("testMybatisCursor总条数>>>>>>>>>>>>>>>>>>>>"+total);
System.out.println("testMybatisCursor方法结束时间>>>>>>>>>>>>>>>" + (end - start));
Thread.sleep(1000);
}
/**
* 正常list测试
*/
@Test
public void testMybatisList() throws IOException, InterruptedException {
Thread.sleep(20000);
long start = System.currentTimeMillis();
SqlSession sqlSession = openSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Person> people = mapper.selectAllList();
long end = System.currentTimeMillis();
System.out.println("testMybatisList总条数>>>>>>>>>>>>>>"+people.size());
System.out.println("方法结束时间>>>>>>>>>>>>>>>>>>>>>>>>" + (end - start));
Thread.sleep(1000);
}
@Before
public void init() {
Reader resourceAsReader = null;
try {
resourceAsReader = Resources.getResourceAsReader("org/apache/ibatis/dao/mapper/mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
resourceAsReader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public SqlSession openSqlSession() {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
我们看一下结果
testMybatisCursor的结果 ,结束时间是3040
testMybatisCursor总条数>>>>>>>>>>>>>>>>>>>>1000000
testMybatisCursor方法结束时间>>>>>>>>>>>>>>>3040
testMybatisList 的结果 3903
testMybatisList总条数>>>>>>>>>>>>>>1000000
方法结束时间>>>>>>>>>>>>>>>>>>>>>>>>3903
目前我们是没有限制内存的,我们可以设置一下堆内存看看,我们将堆内存配置设置为 -Xms500m -Xmx500m 试试看看什么效果
testMybatisCursor的结果 ,结束时间是2489
testMybatisCursor总条数>>>>>>>>>>>>>>>>>>>>1000000
testMybatisCursor方法结束时间>>>>>>>>>>>>>>>2489
testMybatisList 的结果内存溢出了
java.lang.OutOfMemoryError: GC overhead limit exceeded
由上面可以看出Cursor流式查询确实可以提升我们系统的性能和降低内存溢出的可能。由于Cursor是需要事务控制的,如果在系统中出现数据大量的修改等操作的话不建议使用Cursor,Cursor尽量使用小事务的情况。