并发情况下数据校验-基于数据库实现

news2024/9/16 23:47:47

并发情况下数据校验-基于数据库实现

  • 数据库行锁
    • 原理
    • 实际操作
      • 数据准备
      • 开启事务,更新数据
  • 项目实战
    • 项目配置
    • 多线程测试

在开发过程中,我们会遇到校验数据的唯一性,数据更新之后是否超过设置的阈值等等。并发情况下数据校验常见方式有使用分布式锁,数据库行锁等。本章介绍并发情况下使用数据库进行数据校验,常见的场景有:

  1. 金额扣减
  2. 抽奖奖品数量扣减
  3. 库存扣减

数据库行锁

原理

mysql数据库锁
悲观锁&乐观锁
Mysql 如何解决并发更新同一行数据
MySql MVCC 详解

实际操作

mysql 的默认引擎 InnoDB 支持行锁的,本节使用 Mysql 数据库来说明数据库行锁
在这里插入图片描述

数据准备

  1. 创建金额表
create table t_amount (
id int primary key auto_increment,
total_amount decimal(10,2) not null default 0,
used_amount decimal(10,2) not null default 0
) charset = utf8mb4;
  1. 插入测试数据
insert into t_amount(total_amount, used_amount) values(100, 0);
  1. 查询数据表
mysql> select * from t_amount;
+----+--------------+-------------+
| id | total_amount | used_amount |
+----+--------------+-------------+
|  1 |       100.00 |        0.00 |
+----+--------------+-------------+
1 row in set (0.00 sec)

开启事务,更新数据

  1. 打开2个终端,开启事务
start transaction;

在这里插入图片描述
start transaction 语句有啥作用?可以使用 help start transaction 命令查看

mysql> help start transaction
Name: 'START TRANSACTION'
Description:
Syntax:
START TRANSACTION
    [transaction_characteristic [, transaction_characteristic] ...]

transaction_characteristic:
    WITH CONSISTENT SNAPSHOT
  | READ WRITE
  | READ ONLY

BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}

These statements provide control over use of transactions:

o START TRANSACTION or BEGIN start a new transaction.

o COMMIT commits the current transaction, making its changes permanent.

o ROLLBACK rolls back the current transaction, canceling its changes.

o SET autocommit disables or enables the default autocommit mode for
  the current session.

By default, MySQL runs with autocommit mode enabled. This means that as
soon as you execute a statement that updates (modifies) a table, MySQL
stores the update on disk to make it permanent. The change cannot be
rolled back.

To disable autocommit mode implicitly for a single series of
statements, use the START TRANSACTION statement:

START TRANSACTION;
SELECT @A:=SUM(salary) FROM table1 WHERE type=1;
UPDATE table2 SET summary=@A WHERE type=1;
COMMIT;

With START TRANSACTION, autocommit remains disabled until you end the
transaction with COMMIT or ROLLBACK. The autocommit mode then reverts
to its previous state.

START TRANSACTION permits several modifiers that control transaction
characteristics. To specify multiple modifiers, separate them by
commas.

o The WITH CONSISTENT SNAPSHOT modifier starts a consistent read for
  storage engines that are capable of it. This applies only to InnoDB.
  The effect is the same as issuing a START TRANSACTION followed by a
  SELECT from any InnoDB table. See
  http://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html.
  The WITH CONSISTENT SNAPSHOT modifier does not change the current
  transaction isolation level, so it provides a consistent snapshot
  only if the current isolation level is one that permits a consistent
  read. The only isolation level that permits a consistent read is
  REPEATABLE READ. For all other isolation levels, the WITH CONSISTENT
  SNAPSHOT clause is ignored. A warning is generated when the WITH
  CONSISTENT SNAPSHOT clause is ignored.

o The READ WRITE and READ ONLY modifiers set the transaction access
  mode. They permit or prohibit changes to tables used in the
  transaction. The READ ONLY restriction prevents the transaction from
  modifying or locking both transactional and nontransactional tables
  that are visible to other transactions; the transaction can still
  modify or lock temporary tables.

  MySQL enables extra optimizations for queries on InnoDB tables when
  the transaction is known to be read-only. Specifying READ ONLY
  ensures these optimizations are applied in cases where the read-only
  status cannot be determined automatically. See
  http://dev.mysql.com/doc/refman/8.0/en/innodb-performance-ro-txn.html
  for more information.

  If no access mode is specified, the default mode applies. Unless the
  default has been changed, it is read/write. It is not permitted to
  specify both READ WRITE and READ ONLY in the same statement.

  In read-only mode, it remains possible to change tables created with
  the TEMPORARY keyword using DML statements. Changes made with DDL
  statements are not permitted, just as with permanent tables.

  For additional information about transaction access mode, including
  ways to change the default mode, see [HELP ISOLATION].

  If the read_only system variable is enabled, explicitly starting a
  transaction with START TRANSACTION READ WRITE requires the
  CONNECTION_ADMIN or SUPER privilege.

*Important*:

Many APIs used for writing MySQL client applications (such as JDBC)
provide their own methods for starting transactions that can (and
sometimes should) be used instead of sending a START TRANSACTION
statement from the client. See
http://dev.mysql.com/doc/refman/8.0/en/connectors-apis.html, or the
documentation for your API, for more information.

To disable autocommit mode explicitly, use the following statement:

SET autocommit=0;

After disabling autocommit mode by setting the autocommit variable to
zero, changes to transaction-safe tables (such as those for InnoDB or
NDB (http://dev.mysql.com/doc/refman/5.7/en/mysql-cluster.html)) are
not made permanent immediately. You must use COMMIT to store your
changes to disk or ROLLBACK to ignore the changes.

autocommit is a session variable and must be set for each session. To
disable autocommit mode for each new connection, see the description of
the autocommit system variable at
http://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html.

BEGIN and BEGIN WORK are supported as aliases of START TRANSACTION for
initiating a transaction. START TRANSACTION is standard SQL syntax, is
the recommended way to start an ad-hoc transaction, and permits
modifiers that BEGIN does not.

The BEGIN statement differs from the use of the BEGIN keyword that
starts a BEGIN ... END compound statement. The latter does not begin a
transaction. See [HELP BEGIN END].

*Note*:

Within all stored programs (stored procedures and functions, triggers,
and events), the parser treats BEGIN [WORK] as the beginning of a BEGIN
... END block. Begin a transaction in this context with START
TRANSACTION instead.

The optional WORK keyword is supported for COMMIT and ROLLBACK, as are
the CHAIN and RELEASE clauses. CHAIN and RELEASE can be used for
additional control over transaction completion. The value of the
completion_type system variable determines the default completion
behavior. See
http://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html.

The AND CHAIN clause causes a new transaction to begin as soon as the
current one ends, and the new transaction has the same isolation level
as the just-terminated transaction. The new transaction also uses the
same access mode (READ WRITE or READ ONLY) as the just-terminated
transaction. The RELEASE clause causes the server to disconnect the
current client session after terminating the current transaction.
Including the NO keyword suppresses CHAIN or RELEASE completion, which
can be useful if the completion_type system variable is set to cause
chaining or release completion by default.

URL: http://dev.mysql.com/doc/refman/8.0/en/commit.html

默认情况下, mysql 会自动自提交事务,执行 start transaction 不会自动提交事务,需要执行 commit 才会提交事务
在这里插入图片描述

  1. 执行更新数据SQL语句
    终端1执行更新已使用金额(used_amount)语句,终端1不提交事务,终端2也执行同样的sql语句
update t_amount set used_amount = used_amount + 2.11 where id = 1;

在这里插入图片描述
可以看到终端2的更新卡在这里不动了,此时这行数据已经被终端1锁住了,等待一段时间之后,发现终端2报错了
在这里插入图片描述

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

锁等待时间超时,重新开始事务。mysql 锁的超时时间是可以设置的,参考博客mysql修改数据库锁超时时间。

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set, 1 warning (0.20 sec)

查询出锁等待超时时间为50秒。再次在终端2中执行更新sql,在行锁等待时间范围内,提交终端1的事务,此时终端2获取到行锁,可以更新数据了
在这里插入图片描述
3. 校验超过总金额SQL语句
该步骤模拟实际操作中,多个线程并发更新使用金额(used_amount),但是需要保证使用金额小于总金额(total_amount)。使用2个终端,分别开启事务,更新使用金额,在where条件之后校验是否小于等于总金额

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> update t_amount set used_amount = used_amount + 50 where id = 1 and used_amount + 50 <= total_amount;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

终端1执行commit; 之后,可以发现终端2在获取到行锁之后,更新的数据为0条Rows matched: 0 Changed: 0 Warnings: 0, 在业务代码中可以根据jdbc返回的update结果来确定是否更新成功了
在这里插入图片描述
查看此时的金额数据,发现使用金额小于总金额√
在这里插入图片描述

项目实战

在实际的项目使用多线程来测试并发情况下的数据校验,本节使用MyBatis框架来更新数据。

项目配置

  1. mybatis 配置文件 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--

       Copyright 2009-2017 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- autoMappingBehavior should be set in each test case -->
    <properties resource="templates/db.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value=""/>
            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driverClassName}"/>
                <property name="url" value="${jdbcUrl}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/AmountMapper.xml"/>
    </mappers>

</configuration>
  1. 数据库配置文件 db.properties
driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/bootdo?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username=chengdu
password=chengdu
  1. 相关代码文件
    AmountMapper.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.scd.mapper.AmountMapper">
    <update id="updateUsedAmount">
        update t_amount set used_amount = used_amount + #{usedAmount}
        where id = 1 and used_amount + #{usedAmount} <![CDATA[<=]]> total_amount
    </update>
</mapper>

AmountMapper.java 文件

package com.scd.mapper;

import java.math.BigDecimal;

public interface AmountMapper {
    int updateUsedAmount(BigDecimal updateUsedAmount);
}

多线程测试

测试类 AmountTest.java

package com.scd.amount;

import com.scd.mapper.AmountMapper;
import com.scd.sql.SqlTest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Reader;
import java.math.BigDecimal;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class AmountTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(SqlTest.class);

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void setUp() throws Exception {
        String resource = "templates/mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    @Test
    public void testUpdateUsedAmount() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            AmountMapper amountMapper = sqlSession.getMapper(AmountMapper.class);
            int updated = amountMapper.updateUsedAmount(new BigDecimal("50.22"));
            if (updated == 0) {
                LOGGER.info("当前使用金额大于总金额");
            } else {
                LOGGER.info("更新成功");
            }
        }
    }

    @Test
    public void testMultiThreadUpdate() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(100));
        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(() -> {
                try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) {
                    AmountMapper amountMapper = sqlSession.getMapper(AmountMapper.class);
                    int updated = amountMapper.updateUsedAmount(new BigDecimal("20"));
                    sqlSession.commit();
                    if (updated == 0) {
                        LOGGER.info("当前使用金额大于总金额, 执行完成时间 " + System.currentTimeMillis());
                    } else {
                        LOGGER.info("更新成功, 执行完成时间 " + System.currentTimeMillis());
                    }
                } finally {

                    countDownLatch.countDown();
                }
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行 testMultiThreadUpdate 方法,可以看到有部分线程更新成功,有些线程由于超过总金额未更新成功

17:47:43.540 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
17:47:43.568 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
17:47:43.568 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
17:47:43.568 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
17:47:43.568 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
17:47:43.732 [pool-1-thread-1] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
17:47:43.732 [pool-1-thread-3] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
17:47:43.732 [pool-1-thread-2] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
17:47:43.732 [pool-1-thread-5] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
17:47:43.732 [pool-1-thread-4] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
17:47:44.772 [pool-1-thread-1] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1212136459.
17:47:44.772 [pool-1-thread-1] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@483fbc0b]
17:47:44.776 [pool-1-thread-1] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==>  Preparing: update t_amount set used_amount = used_amount + ? where id = 1 and used_amount + ? <= total_amount
17:47:44.792 [pool-1-thread-4] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 538640972.
17:47:44.792 [pool-1-thread-4] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@201b024c]
17:47:44.792 [pool-1-thread-4] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==>  Preparing: update t_amount set used_amount = used_amount + ? where id = 1 and used_amount + ? <= total_amount
17:47:44.804 [pool-1-thread-4] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==> Parameters: 20(BigDecimal), 20(BigDecimal)
17:47:44.804 [pool-1-thread-1] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==> Parameters: 20(BigDecimal), 20(BigDecimal)
17:47:44.812 [pool-1-thread-4] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - <==    Updates: 1
17:47:44.812 [pool-1-thread-4] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@201b024c]
17:47:44.816 [pool-1-thread-5] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 149901742.
17:47:44.816 [pool-1-thread-5] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef51ae]
17:47:44.816 [pool-1-thread-5] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==>  Preparing: update t_amount set used_amount = used_amount + ? where id = 1 and used_amount + ? <= total_amount
17:47:44.816 [pool-1-thread-5] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==> Parameters: 20(BigDecimal), 20(BigDecimal)
17:47:44.824 [pool-1-thread-2] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 529041956.
17:47:44.824 [pool-1-thread-2] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1f888a24]
17:47:44.824 [pool-1-thread-2] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==>  Preparing: update t_amount set used_amount = used_amount + ? where id = 1 and used_amount + ? <= total_amount
17:47:44.828 [pool-1-thread-2] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==> Parameters: 20(BigDecimal), 20(BigDecimal)
17:47:44.836 [pool-1-thread-3] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1100293226.
17:47:44.836 [pool-1-thread-3] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4195246a]
17:47:44.836 [pool-1-thread-3] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==>  Preparing: update t_amount set used_amount = used_amount + ? where id = 1 and used_amount + ? <= total_amount
17:47:44.836 [pool-1-thread-3] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - ==> Parameters: 20(BigDecimal), 20(BigDecimal)
17:47:44.967 [pool-1-thread-4] INFO com.scd.sql.SqlTest - 更新成功, 执行完成时间 1708854464967
17:47:44.967 [pool-1-thread-1] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - <==    Updates: 1
17:47:44.967 [pool-1-thread-4] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@201b024c]
17:47:44.967 [pool-1-thread-1] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@483fbc0b]
17:47:44.967 [pool-1-thread-4] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@201b024c]
17:47:44.967 [pool-1-thread-4] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 538640972 to pool.
17:47:45.033 [pool-1-thread-5] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - <==    Updates: 0
17:47:45.033 [pool-1-thread-1] INFO com.scd.sql.SqlTest - 更新成功, 执行完成时间 1708854465033
17:47:45.033 [pool-1-thread-1] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@483fbc0b]
17:47:45.033 [pool-1-thread-5] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef51ae]
17:47:45.033 [pool-1-thread-1] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@483fbc0b]
17:47:45.033 [pool-1-thread-2] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - <==    Updates: 0
17:47:45.033 [pool-1-thread-1] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1212136459 to pool.
17:47:45.033 [pool-1-thread-5] INFO com.scd.sql.SqlTest - 当前使用金额大于总金额, 执行完成时间 1708854465033
17:47:45.033 [pool-1-thread-2] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1f888a24]
17:47:45.033 [pool-1-thread-5] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef51ae]
17:47:45.033 [pool-1-thread-5] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8ef51ae]
17:47:45.033 [pool-1-thread-2] INFO com.scd.sql.SqlTest - 当前使用金额大于总金额, 执行完成时间 1708854465033
17:47:45.033 [pool-1-thread-5] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 149901742 to pool.
17:47:45.033 [pool-1-thread-3] DEBUG com.scd.mapper.AmountMapper.updateUsedAmount - <==    Updates: 0
17:47:45.033 [pool-1-thread-3] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4195246a]
17:47:45.033 [pool-1-thread-2] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1f888a24]
17:47:45.033 [pool-1-thread-3] INFO com.scd.sql.SqlTest - 当前使用金额大于总金额, 执行完成时间 1708854465033
17:47:45.033 [pool-1-thread-3] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4195246a]
17:47:45.033 [pool-1-thread-2] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1f888a24]
17:47:45.033 [pool-1-thread-2] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 529041956 to pool.
17:47:45.037 [pool-1-thread-3] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4195246a]
17:47:45.037 [pool-1-thread-3] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1100293226 to pool.

查询数据表t_amount
在这里插入图片描述
未执行多线程测试方法之前 used_amount 的金额为 54.22, 执行完成之后,通过运行日志可以确定有2个线程执行成功了,使用金额变成了94.22

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1471564.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

辽宁博学优晨教育视频剪辑培训探索学习新意义

在当今数字化快速发展的时代&#xff0c;视频已成为信息传达的重要载体。辽宁博学优晨教育视频剪辑培训应运而生&#xff0c;不仅为学员提供了专业的学习平台&#xff0c;更在探索学习的意义方面赋予了新的内涵。 视频剪辑作为现代媒体行业的重要技能&#xff0c;其学习意义不仅…

高速稳定、网络隔离,解析“向日葵控控”远控方案在医疗行业应用

在医疗大健康领域&#xff0c;依托高速发展的信息化技术加速布局智能化&#xff0c;通过远程手段提高医疗服务质量、促进医疗资源共享、提升医疗工作效率&#xff0c;已成为医院和各类社区诊所等提供关键医疗服务部门近年来的发展目标之一。 同时&#xff0c;根据医疗领域的特殊…

点云数据处理常用外部库(C++/Windows)的项目配置

一、点云数据处理常用外部库&#xff08;C版本&#xff09;的下载安装与项目配置 &#xff08;一&#xff09;PCL 基于VS2019编程平台的PCL外部库下载安装及项目配置已有大量博客&#xff0c;本文不再赘述。具体下载安装及项目配置流程可参考外部库编译配置参考资料/*1*/ 。需…

linux+fortify

系统版本:Ubuntu22.04桌面版 一:登录root用户关闭防火墙 sudo passwd root systemctl stop ufw.service systemctl disable ufw.service 二:开启ssh服务 sudo apt update sudo apt install openssh-server sudo systemctl start ssh sudo systemctl status ssh连接上WinS…

开年大吉!安全狗入选工信部工业互联网试点示范名单

近日&#xff0c;工业和信息化部信息通信管理局公布了2023年工业互联网试点示范名单。此次名单根据《工业和信息化部办公厅关于组织开展2023年工业互联网试点示范项目申报工作的通知》&#xff08;工信厅信管函﹝2023﹞319号&#xff09;&#xff0c;经企业申报、地方推荐、专家…

亚马逊测评 能让买家更快速的喜欢上你的产品,提高转化率

在当今的电子商务时代&#xff0c;亚马逊作为全球最大的在线零售商之一&#xff0c;已经成为了消费者购买各种商品的首选平台。然而&#xff0c;对于消费者来说&#xff0c;如何选择适合自己的产品成为了他们面临的一大难题。因此&#xff0c;本文将介绍亚马逊上如何让买家通过…

盲盒小程序开发:探索技术与创意的融合

盲盒&#xff0c;这种充满惊喜和趣味性的商品销售模式&#xff0c;近年来在国内市场迅速崛起。它结合了收藏、交换、抽奖等多种消费体验&#xff0c;吸引了大量年轻消费者。随着移动互联网的普及&#xff0c;盲盒小程序的开发为这一市场注入了新的活力。本文将探讨盲盒小程序开…

yolov8学习笔记(二)模型训练

目录 yolov8的模型训练 1、制作数据集&#xff08;标记数据集&#xff09; 2、模型训练&#xff08;标记数据集、参数设置、跟踪模型随时间的性能变化&#xff09; 2.1、租服务器训练 2.2、加训练参数 2.3、看训练时的参数&#xff08;有条件&#xff0c;就使用TensorBoard&…

BFS中的多源BFS-双端队列BFS

2024-01-30(树与图的深度优先遍历、广度优先遍历、拓扑排序&#xff09;-CSDN博客 2024-01-31&#xff08;最短路径&#xff09;-CSDN博客 多源BFS 173. 矩阵距离 - AcWing题库 求出每个位置到所有为1的点的最短距离 import java.util.*; import java.io.*;class PII{int…

linux系统---安装使用nginx

目录 一、编译安装Nginx 1、关闭防火墙&#xff0c;将安装nginx所需要软件包传到/opt目录下 ​编辑2、安装依赖包 3、创建运行用户、组 4、编译安装nginx 5、创建软链接后直接nginx启动 ​编辑 6、创建nginx自启动文件 ​编辑6.1 重新加载配置、设置开机自启并开启服务…

LeetCode_Java_动态规划系列(1)(题目+思路+代码)

目录 斐波那契类型 746.使用最小花费爬楼梯 矩阵 120. 三角形最小路径和 斐波那契类型 746.使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。…

知网G4《教育界》正规期刊吗?投稿难吗?

知网G4《教育界》正规期刊吗&#xff1f;投稿难吗&#xff1f; 《教育界》杂志&#xff0c;定位于教育专业的教育类权威性学术期刊&#xff0c;教育类核心期刊&#xff0c;本刊是经国家新闻出版出版总署批准的纯教育类权威期刊。 《教育界》知网G4,刊期24年底&#xff0c;收幼…

【ArcGIS】基于DEM/LUCC等数据统计得到各集水区流域特征

基于DEM/LUCC等数据统计得到各集水区流域特征 提取不同集水区各类土地利用类型比例步骤1&#xff1a;划分集水区为独立面单元步骤2&#xff1a;批量掩膜提取得到各集水区土地利用类型比例步骤3&#xff1a;导入各集水区LUCC数据并统计得到各类型占比 提取坡度特征提取河网密度特…

SQL-多表查询

多表关系 一对多&#xff08;多对一&#xff09; 在多的一方建立外键&#xff0c;指向一的一方的主键。多对多 建立第三张中间表&#xff0c;中间表至少包含两个外键&#xff0c;分别关联两方主键。一对一 在任意一方加入外键&#xff0c;关联另外一方的主键&#xff0c;并且设…

【算法 - 动态规划】找零钱问题Ⅰ

在前面的动态规划系列文章中&#xff0c;关于如何对递归进行分析的四种基本模型都介绍完了&#xff0c;再来回顾一下&#xff1a; 从左到右模型 &#xff1a;arr[index ...] 从 index 之前的不用考虑&#xff0c;只考虑后面的该如何选择 。范围尝试模型 &#xff1a;思考 [L ,…

[面试]我们常说的负载均衡是什么东西?

什么是负载均衡 如果用户量很多, 服务器的流量也随之增大, 此时出现两个问题, 软件性能下降 容易出现单点故障 为了解决这些问题, 引入了集群化架构, 也就是把一个软件同时部署在多个服务器上 集群化架构出现的问题 架构改变后又出现了两个问题 如何将请求均匀的发送到多…

代码随想录day34--动态规划的应用2 | LeetCode343.整数拆分、LeetCode96.不同的二叉搜索树

LeetCode343.整数拆分 题目描述&#xff1a; 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。…

mysql数据同步组件

# 要求&#xff1a; 支持实时同步&#xff0c; 支持增量同步&#xff0c; 不用写业务逻辑&#xff0c; 支持Mysql之间同步&#xff0c; 活跃度高。 # 可选组件&#xff1a;canal ,debezium, datax , Databus, Flinkx , Bifrost &#xff1a; Bifrost 特点&#xff1a; 1. …

Stable Diffusion 绘画入门教程(webui)-ControlNet(线稿约束)

上篇文章介绍了openpose&#xff0c;本篇文章介绍下线稿约束&#xff0c;关于线稿约束有好几个处理器都属于此类型&#xff0c;但是有一些区别。 包含&#xff1a; 1、Canny(硬边缘&#xff09;&#xff1a;识别线条比较多比较细&#xff0c;一般用于更大程度得还原照片 2、ML…

2024年西安市省级工业互联网平台申报条件材料、时间流程

一、申报条件 (一)申报单位须在西安市内登记注册,具有独立的法人资格和规范的财务管理制度,无不良信用记录,照章依法纳税。 (二)面向陕西省支持的35个工业重点产业方向内的中小企业提供服务,围绕各类型工业场景,加快企业内部信息化系统综合集成和云化改造,连接设备、软件、工…