我对autocommit以及select语句是否需要加事务的一点理解

本文探讨了MySQL中的autocommit特性和事务使用,解释了autocommit如何影响DML语句,并讨论了在不同隔离级别下读写操作的一致性和原子性。在执行SQL语句时,数据库是否会自动开启事务存在争议,但实践表明,即使不显式开启事务,DML操作的结果也会被提交或回滚。在使用Hibernate时,对于查询操作无需手动管理事务,而对于DML操作则建议显式控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在复习hibernate以及数据库事务和隔离级别的相关知识,有了一点新的理解。mysql可以通过下面的命令:查询和修改当前客户端连接的autocommit。0代表不自动提交,1代表自动提交。


什么是自动提交呢?对于insert、update、delete这些DML语句来说,如果没有提交,被修改的记录处于uncommited状态的;如果提交了,那么处于commited状态的。下面的客户端1和2设置的事务隔离级别都是repeatable read,所以客户端2看不到客户端1的修改。如果客户端1打开了autocommit,那么客户端2就可以查看到客户端1修改后的结果。

     


mysql如果我们想开启一个事务,可以通过start  transaction;如果提交这个事务用commit;如果需要放弃回滚,那么可以使用rollback。

#提交一个事务
start  transaction;
	#some sql statements
commit;

#回滚事务
start  transaction;
	#some sql statements
rollback;

可以看到autocommit和事务很像,我自己认为:执行第一条SQL语句的时候,如果我们没有显示地开启事务,那么数据库服务器会自动帮我们开启一个事务;如果打开了autocommit,每一条sql执行结束的时候,数据库服务器会自动关闭事务。如果关闭了autocommit,那么数据库就不会自动帮我们提交事务。


也就是说:如果开启了autocommit,数据库服务器自动开启事务(每一条sql语句开始执行的时候),自动提交事务(sql语句执行成功),自动回滚事务(sql语句执行失败)。很显然:autocommit没有什么实际意义,如果要使用事务,就必需关闭autocommit,不然每一条sql都是一个独立的事务,而实际上事务包含了一组sql语句。最佳实践:对于DML操作,我们需要显示开始、显示提交或者回滚事务,哪儿怕事务里面只有一条sql语句

public void testHibernateSave() {
	Student student = new Student();
	student.setName("saveWithHibernate");
	student.setAge(3);

	Session session = sessionFactory.openSession();
	session.beginTransaction();//开启事务
	session.save(student);

	session.save(student);

	session.getTransaction().commit();//提交事务
	session.close();
}


现在我们理解autocommit和事务了,那我们再看一下select语句,select语句需要加事务吗?
public void testSelect() {
	Session session = sessionFactory.openSession();
	Student student = (Student) session.get(Student.class, 100);
	System.out.println(student);
	session.close();
}


public void testSelectWithTransaction() {
	Session session = sessionFactory.openSession();
	session.beginTransaction();//开启事务

	Student student = (Student) session.load(Student.class, 100);

	System.out.println(student);
	
	session.getTransaction().commit();//提交事务
	session.close();
}
上面的2种查询方式,都能从数据库查出我们需要的结果。那到底select的时候需不要加事务呢?这个问题,网上有很多争论,有人说需要加,有人说不需要,比如iteye上的这个帖子。我来谈下自己的看法,不一定对,有错误的话欢迎指正。

读操作最重要的是什么呢?一致性。写操作最重要的是什么吗呢?原子性。如果一个客户端(占用一个数据库连接),只需执行要一条select,那么显然加不加事务没有任何影响。如果一个客户端,需要执行多条select,对于这个客户端来说,最重要的就是读的一致性了。比如我第一次读取这条记录,第二次再次读取这条记录,客户端肯定希望这2次的读取结果是一模一样的。



客户端1的事务隔离级别是:REPEATABLE-READ,关闭了autocommit,如果客户端1没有显示地commit,那么永远也看不见客户端2修改后的数据。如果客户端1显示提commit之后,再次查询,就能够读取到客户端2修改后的结果了。我们知道:一个数据库连接可以开启多个数据库事务。至于一个数据库事务里面多次查询结果是否一致,是取决于事务隔离级别的。不同的隔离级别能够解决不同的数据库并发问题:



所以我认为:如果我们没有显示地开启事务,那么当执行第一条sql语句(不管是select,还是DML)都会开启一个事务。如果打开了autocommit,那么sql执行成功后会自动提交;如果关闭了autocommit,那么我们必须要显示地commit或者rollback。

public void testSelect() {
	Session session = sessionFactory.openSession();
	Student student = (Student) session.get(Student.class, 100);
	System.out.println(student);
	session.close();
}

hibernate默认会关闭autocommit,上面的代码没有显示开启和提交事务。当执行select的时候,底层的数据库连接会自动开启事务,我们没有显示地提交事务,这是因为session.close会释放底层的数据库连接,数据库连接被释放,没有提交的事务会被自动回滚。所以使用hibernate的时候,DML操作我们需要显示地开启事务和提交事务,如果是查询操作不需要显示地开启和提交事务


写到这里,有一个地方是存在争议的:如果没有显示地开启事务,mysql数据库到底会不会在执行第一条SQL的时候自动开始事务。从上面的测试结果来看,应该是会的,或者说至少也会开启一个类似事务的东东(出于性能的考虑)。因为我们没有显示地开启事务,执行DML语句后commit,这些修改确实被永久保存到了数据库。而且执行select后,如果不commit,那么后续的select永远也看不到另一个客户端的修改结果。从表现上来看,这跟事务没有什么两样。


<think>我们有两个主要任务: 1. 设置当前会话为手动提交事务模式。 2. 在事务中执行DELETE语句。 根据引用[1]和引用[4],我们可以通过以下两种方式之一来显式开启事务并控制提交: - 使用`BEGIN`或`START TRANSACTION`来开始一个事务,然后使用`COMMIT`或`ROLLBACK`来结束事务。 - 或者,先设置当前会话的自动提交为关闭(`SET AUTOCOMMIT=0`),然后执行一系列语句,最后手动执行`COMMIT`或`ROLLBACK`。 但是,根据用户需求,我们是要设置当前会话为手动提交事务模式,并在事务内执行删除语句。这里我们可以选择第二种方式,即先关闭自动提交,然后执行DELETE,再手动提交。 步骤: 1. 设置当前会话为手动提交模式:`SET SESSION autocommit = 0;` 或 `SET autocommit = 0;` (因为SESSION是默认的) 2. 执行DELETE语句。 3. 如果确认删除,则提交事务:`COMMIT;` 4. 如果不想删除,则回滚事务:`ROLLBACK;` 另外,也可以使用显式事务(BEGIN或START TRANSACTION)而不改变autocommit的设置。因为即使autocommit是开启的,使用START TRANSACTION也会暂时关闭自动提交直到事务结束。 两种方法都可以,但根据用户要求“设置当前会话手动提交事务”,我们更倾向于使用设置autocommit=0的方式,因为这样设置后,当前会话的所有事务都需要手动提交。 注意:设置autocommit=0后,当前会话中执行的所有写操作(如DELETE, UPDATE, INSERT)都会在一个事务中,直到显式执行COMMIT或ROLLBACK,或者执行了下一个事务开始语句(如START TRANSACTION)或DDL语句(会自动提交当前事务)等。 具体操作如下: 步骤1:设置当前会话手动提交模式 ```sql SET autocommit = 0; -- 或者 SET SESSION autocommit = 0; ``` 步骤2:执行DELETE语句 ```sql DELETE FROM 表名 WHERE 条件; ``` 步骤3:检查删除的数据是否正确,如果正确则提交 ```sql COMMIT; ``` 或者,如果发现删除错误,则回滚: ```sql ROLLBACK; ``` 另外,也可以使用显式事务块(不改变autocommit的默认设置): ```sql START TRANSACTION; -- 或者 BEGIN DELETE FROM 表名 WHERE 条件; -- 然后根据情况选择提交或回滚 COMMIT; -- 或 ROLLBACK; ``` 两种方式的选择: - 第一种方式(设置autocommit=0)会使得当前会话后续的所有操作都在事务中(直到显式提交或回滚),所以需要谨慎使用,避免长时间不提交导致锁持有时间过长。 - 第二种方式(使用START TRANSACTION)只影响当前事务,之后的操作又会回到自动提交模式(如果autocommit原来是1的话)。 根据用户需求,如果只是针对当前这个删除操作需要手动控制事务,建议使用第二种方式(START TRANSACTION)以避免影响后续操作。 但用户明确要求“设置当前会话为手动提交事务模式”,所以这里我们使用第一种方式。 然而,需要注意的是,设置autocommit=0后,每次执行一条写操作都会开启一个事务,并且不会自动提交,需要手动提交。如果执行了多条写操作,它们会属于同一个事务(除非手动提交或回滚后,再执行的下一条写操作会开启新的事务)。所以,在完成删除操作并提交后,如果后续操作希望恢复自动提交,可以设置回`autocommit=1`。 因此,完整步骤: 1. 设置当前会话为手动提交模式: ```sql SET autocommit = 0; ``` 2. 执行删除操作: ```sql DELETE FROM employees WHERE id = 1001; ``` 3. 确认删除(提交)或撤销(回滚): ```sql COMMIT; -- 或 ROLLBACK; ``` 4. (可选)恢复自动提交模式(如果后续操作希望自动提交): ```sql SET autocommit = 1; ``` 但是,如果只是临时针对一个事务,建议使用显式事务(START TRANSACTION)而不改变autocommit的设置。这样不会影响当前会话的其他操作。 考虑到用户要求设置当前会话为手动提交模式,我们按照设置autocommit=0的方式给出示例,并在最后恢复自动提交。 示例: 假设我们要在employees表中删除id为1001的员工记录。 ```sql -- 1. 设置当前会话手动提交 SET SESSION autocommit = 0; -- 2. 执行删除 DELETE FROM employees WHERE id = 1001; -- 3. 检查删除是否是我们想要的(这里没有实际检查,只是示意) -- 如果确认删除,则提交 COMMIT; -- 如果发现删除错误,则回滚 -- ROLLBACK; -- 4. 恢复自动提交(避免影响后续操作) SET autocommit = 1; ``` 注意:在同一个会话中,如果执行了多个事务,每个事务都需要在写操作后执行COMMIT或ROLLBACK。在设置autocommit=0后,每次执行写操作(DELETE/UPDATE/INSERT)都会开启一个新事务(如果前一个事务已经结束的话)。所以,在第一个事务提交后,再执行另一个写操作,就会开启第二个事务,同样需要手动提交。 为了安全起见,建议在完成需要手动控制的事务后,将autocommit设置回1。 另外,引用[3]中展示了一个例子:设置session autocommit=0,然后执行delete,再执行select,但并没有立即提交。这个例子也说明了设置后需要手动提交。 总结:根据需求,我们使用设置autocommit=0的方式将会话设置为手动提交模式,然后执行DELETE,最后根据情况提交或回滚,并建议在完成后恢复自动提交。 相关问题: 1. 手动提交事务和自动提交事务有什么区别? 2. 在手动提交模式下,执行多个DML语句会怎样? 3. 如何查看当前会话的自动提交设置? 4. 在事务中执行DELETE语句后,如何查看未提交的数据变更? 5. 事务提交后能否回滚?</think>在 MySQL 中设置当前会话为手动提交事务模式并在事务中执行 DELETE 语句,需按照以下步骤操作: ### 🛠️ 完整操作步骤 1. **开启手动事务模式** 设置当前会话禁用自动提交: ```sql SET SESSION autocommit = 0; -- 关闭当前会话自动提交[^1][^4] ``` 2. **显式开启事务** 使用 `START TRANSACTION` 或 `BEGIN` 明确事务开始: ```sql START TRANSACTION; -- 或使用 BEGIN[^1][^4] ``` 3. **执行 DELETE 语句** 在事务内执行删除操作: ```sql DELETE FROM employees WHERE department = 'HR' AND join_date < '2020-01-01'; -- 示例删除条件[^5] ``` 4. **验证操作结果** 检查受影响的行数或查询数据: ```sql SELECT ROW_COUNT(); -- 查看删除行数 SELECT * FROM employees WHERE department = 'HR'; -- 验证数据 ``` 5. **提交或回滚事务** 根据操作结果决定: ```sql COMMIT; -- 确认删除,持久化更改[^2][^5] -- 或 ROLLBACK; -- 撤销所有删除操作[^4][^5] ``` ### ⚠️ 关键注意事项 1. **事务边界控制** - 手动提交模式下,必须显式使用 `COMMIT` 才能使操作生效 - 未提交的修改仅当前会话可见,其他会话无法看到[^3] 2. **锁机制影响** DELETE 操作会获取行锁,长时间未提交可能导致: - 其他会话的查询阻塞 - 锁等待超时错误(默认 50 秒) 3. **连接与会话** 手动提交设置仅对当前连接有效: ```sql SELECT @@autocommit; -- 查看当前自动提交状态 ``` 断开连接后自动恢复为全局设置(通常 autocommit=1) 4. **错误处理建议** 添异常处理确保事务完整性: ```sql START TRANSACTION; BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END; -- 执行DELETE操作 COMMIT; END; ``` ### 💡 完整操作示例 ```sql -- 步骤1: 设置手动提交模式 SET SESSION autocommit = 0; -- 步骤2: 开启事务 START TRANSACTION; -- 步骤3: 执行删除 DELETE FROM audit_log WHERE operation_date < DATE_SUB(NOW(), INTERVAL 1 YEAR); -- 步骤4: 验证删除行数 SELECT ROW_COUNT() AS deleted_rows; -- 步骤5: 提交事务 COMMIT; -- 恢复自动提交(可选) SET SESSION autocommit = 1; ``` ### 📚 事务工作原理图解 ```mermaid sequenceDiagram participant Client as 客户端会话 participant MySQL as MySQL服务器 Client->>MySQL: SET autocommit=0 MySQL-->>Client: 确认关闭自动提交 Client->>MySQL: START TRANSACTION Client->>MySQL: DELETE FROM table... MySQL-->>Client: 返回受影响行数 alt 操作成功 Client->>MySQL: COMMIT MySQL-->>Client: 确认持久化 else 操作失败 Client->>MySQL: ROLLBACK MySQL-->>Client: 确认回滚 end ``` ### ❓ 相关问题 1. 如何查看 MySQL 中当前活跃的事务? 2. 事务隔离级别对 DELETE 操作有何影响? 3. 大表删除数据时如何避免锁表? 4. 如何实现跨会话的事务一致性? 5. 使用 MySQL 事务时常见的性能瓶颈有哪些? > **提示**:生产环境执行批量删除前,建议先用 `SELECT` 验证条件语句,避免误删数据。事务操作完成后应及时提交并恢复自动提交模式,避免长期持有锁[^1][^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值