class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">CREATE TABLE accounts ( class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line"> id INT PRIMARY KEY, class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line"> balance DECIMAL(10, 2) NOT NULL class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">); class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">INSERT INTO accounts (id, balance) VALUES (1, 1000.00), (2, 500.00); class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">START TRANSACTION; class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="14"> class="hljs-ln-code"> class="hljs-ln-line">UPDATE accounts SET balance = balance - 100 WHERE id = 1; class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="15"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="16"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="17"> class="hljs-ln-code"> class="hljs-ln-line">UPDATE accounts SET balance = balance + 100 WHERE id = 2; class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="18"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="19"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="20"> class="hljs-ln-code"> class="hljs-ln-line">COMMIT; class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="21"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="22"> class="hljs-ln-code"> class="hljs-ln-line"> class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="23"> class="hljs-ln-code"> class="hljs-ln-line">SELECT * FROM accounts; class="hide-preCode-box">
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
代码说明
-
创建账户表:首先我们创建了一个名为accounts
的表,包含账户ID(id
)和余额(balance
)两个字段。
-
插入示例账户:插入两个示例账户,账户A的余额为1000元,账户B的余额为500元。
-
开始事务:通过START TRANSACTION;
开始一个新的事务,用于保证接下来的操作的原子性。
-
更新账户余额:
- 第一个
UPDATE
语句用于从账户A中扣除100元。 - 第二个
UPDATE
语句用于将100元存入账户B。
-
提交事务:通过COMMIT;
来提交事务,如果所有操作成功执行,所有的变更会被保存到数据库中。
-
查询结果:最后,使用SELECT语句检查账户的余额,以确认转账是否成功。
错误处理
在实际生产环境中,还需要考虑错误处理。如果在任一步骤发生错误,你应该执行ROLLBACK;
来撤销之前的操作。例如:
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="1"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="2"> class="hljs-ln-code"> class="hljs-ln-line">BEGIN;
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="3"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="4"> class="hljs-ln-code"> class="hljs-ln-line">UPDATE accounts SET balance = balance - 100 WHERE id = 1;
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="5"> class="hljs-ln-code"> class="hljs-ln-line">IF ROW_COUNT() = 0 THEN
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="6"> class="hljs-ln-code"> class="hljs-ln-line"> ROLLBACK;
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="7"> class="hljs-ln-code"> class="hljs-ln-line"> SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Transaction failed: Account A does not have enough balance';
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="8"> class="hljs-ln-code"> class="hljs-ln-line">END IF;
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="9"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="10"> class="hljs-ln-code"> class="hljs-ln-line">UPDATE accounts SET balance = balance + 100 WHERE id = 2;
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="11"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="12"> class="hljs-ln-code"> class="hljs-ln-line">
- class="hljs-ln-numbers"> class="hljs-ln-line hljs-ln-n" data-line-number="13"> class="hljs-ln-code"> class="hljs-ln-line">COMMIT;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}" onclick="hljs.signin(event)">
这种情况下,如果账户A的余额不足,事务将被回滚,确保数据一致性。通过合理使用事务,能够保障银行等业务场景中的数据安全。
在高并发场景使用事务会出现的问题以及解决方法
在高并发场景中,使用事务会面临一些挑战,这些挑战主要来源于多个事务并发执行时的相互影响。主要问题包括:
1. 死锁(Deadlock)
多个事务在互相等待对方释放资源,导致系统无法继续执行。
解决方案:
- 超时机制:设置事务超时,避免长时间等待。
- 死锁检测与回滚:数据库通常会自动检测到死锁并回滚某一个事务,开发者可以优化代码逻辑以减少死锁发生的概率。
- 合理的锁顺序:在数据库操作中,保持一致的资源访问顺序,降低死锁的风险。
2. 可用性和性能下降
事务的隔离级别越高,可用性和性能通常越低。比如,使用Serializable
级别会导致较长的锁定时间,影响并发性能。
解决方案:
- 调整隔离级别:根据业务需求,选择适当的隔离级别。例如,使用
读已提交
或可重复读
,在性能和隔离性之间找到平衡。 - 使用乐观锁:在更新数据时,不会立即加锁,只有在提交时检查数据是否发生变化。如果发生变化,则回滚并重试。可以通过使用版本号或时间戳实现乐观锁。
3. 脏读和不可重复读
在高并发的情况下,可能会出现脏读(一个事务读取另一个事务未提交的数据)和不可重复读(同一事务的多次查询结果不同)。
解决方案:
- 选择合适的隔离级别:如前所述,通过选择
读已提交
或可重复读
来避免脏读和不可重复读问题。 - 使用锁机制:在需要保证数据一致性的场合应用行级锁,避免其他事务读取未提交的数据。
4. 幻读(Phantom Read)
在启动事务后,另一事务的插入会导致当前事务再次查询时出现不同的记录集。
解决方案:
- 使用更高的隔离级别:如
Serializable
可以完全避免幻读,但在高并发场景中会导致性能下降。 - 使用范围锁(如在某些数据库中支持),通过对查询的值范围加锁来避免幻读的发生。
5. 回滚带来的性能开销
在高并发的情况下,频繁的事务回滚会导致性能下降。
解决方案:
- 错误重试机制:在应用层实现智能重试机制,处理因冲突引起的回滚,同时通过后退指数等策略减少冲突。
- 提高数据库性能:优化数据库性能、索引、查询,减少回滚的可能性。
总结
在高并发场景使用事务时,必须权衡数据一致性和系统性能。通过选择合适的事务隔离级别、利用锁机制、优化数据库操作以及设计合理的应用逻辑,可以减少并发带来的问题。同时,监控系统性能和及时调整也是确保系统稳定的重要手段。
MySQL自带的隔离级别调整
事务中的隔离性是指在多用户并发执行事务时,每个事务的执行不会被其他事务所干扰,确保每个事务可以在其独立的环境中执行。这一特性对于维护数据的完整性和一致性至关重要。隔离性通常通过不同的隔离级别来实现,每个级别都在并发性和数据一致性之间取得不同的平衡。
MySQL支持四种主要的事务隔离级别:
-
读未提交(Read Uncommitted):
- 事务可以读取其他事务未提交的数据。
- 这种级别的并发性最高,但可能会导致脏读(Dirty Read)现象,即一个事务读取到另一个事务尚未提交的数据,这可能在后续操作中导致错误。
-
读已提交(Read Committed):
- 事务只能读取其他已提交事务的数据。
- 这种级别避免了脏读,但仍然可能出现不可重复读(Non-repeatable Read),即在同一事务内的两次读取可能得到不同的结果,因为其他事务在这段时间可以提交改变。
-
可重复读(Repeatable Read):
- 事务在开始时读取的数据在整个事务期间都是一致的,即使其他事务提交了改变。
- 这种级别避免了脏读和不可重复读,但可能发生幻读(Phantom Read),即在同一事务中,后续查询可能会看到新插入的行。
-
串行化(Serializable):
- 这是最高级别的隔离,事务完全串行执行,仿佛是依次执行的。
- 这种级别可以完全避免脏读、不可重复读和幻读,但会导致并发性能显著降低,因为事务必须等待彼此完成。
隔离性的影响
- 性能:较高的隔离级别通常意味着更低的并发性能,因为事务之间的干扰被限制得更严格。
- 一致性:在需要高度一致性保障的场景下,选择较高的隔离级别是合理的,比如金融交易系统等。而在一些可以容忍一定不一致性的场景下,选择较低的隔离级别可以提高系统性能。
- 设计考虑:系统的设计和需求会决定使用哪种隔离级别。开发者需要根据应用场景评估性能与一致性之间的权衡。
总之,事务中的隔离性是支持并发操作的重要机制,通过合理选择隔离级别,可以在系统性能和数据一致性之间找到适当的平衡。
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/yican2580/article/details/141170773","extend1":"pc","ab":"new"}">>
id="blogVoteBox" style="width:400px;margin:auto;margin-top:12px" class="blog-vote-box"> class="vote-box csdn-vote" style="opacity: 1;">
class="pos-box pt0" style="height: 222px; visibility: visible;">
评论记录:
回复评论: