Spring Boot Hibernate在MySQL中的乐观锁案例死锁

问题描述

我有一个关于乐观锁死锁的问题。 我实施了一个简单的机票预订系统。这样的数据库方案:

enter image description here

在预订航班API中,该方法将检查航班是否可用并获得版本号。然后,该方法将从available_seats减去1并检查版本号是否相同。否则,save()方法将抛出ObjectOptimisticLockingFailureException并被控制器层捕获。代码是服务层:

    public Ticket bookFlight(Flight flight,long customerId) throws Exception {
        Flight returnedFlight = flightRepository.findFlightByFlightNumberAndFlightDate(flight.getFlightNumber(),flight.getFlightDate());
        if (validFlightIsAvailable(returnedFlight) == false) {
            // A valid flight ticket id should greater than 0.
            // Therefore,ticketId = 0 means the flight is full.
            int fullFlightTicketId = 0;
            log.info("Customer " + customerId + " failed to book the ticket because the flight is full.");
            return new Ticket(fullFlightTicketId);
        } else {
            Ticket newTicket = new Ticket(customerId,returnedFlight.getFlightId(),returnedFlight.getFlightDate());
            if (checkIsDuplicatedBooking(newTicket) == true){
                log.error("The customer (" + customerId + ") already book the ticket for flight " + newTicket.getFlightId());
                throw new ClientException("Customer already booked the ticket in the same flight",HttpStatus.BAD_REQUEST);
            }
            returnedFlight.setAvailableSeats(returnedFlight.getAvailableSeats() - 1);
            Flight newFlight = flightRepository.save(returnedFlight);
            if (newFlight == null) {
                log.error("Update flight's available seats error. (flight Id: " +
                        returnedFlight.getFlightId() + "). Rollback all transactions.");
                throw new ServerException("Unknown Server Exception.",HttpStatus.INTERNAL_SERVER_ERROR);
            }
            Ticket returnedTicket = ticketRepository.save(newTicket);
            if (returnedTicket != null) {
                log.info("Customer Id: " + customerId + " successfully booked the ticket in flight " + flight.getFlightId());
                return returnedTicket;
            } else {
                log.error("Save new ticket into database error. (Customer Id: " + customerId + ",flight Id: " +
                        returnedFlight.getFlightId() + "). Rollback all transactions.");
                throw new ServerException("Unknown Server Exception.",HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }
    }

航班实体为:

@Entity
@Table(name = "flight")
public class Flight {

    @Id
    @GeneratedValue(strategy =  GenerationType.AUTO)
    @Column(name = "flight_id",nullable = false)
    private Long flightId;

    @Column(name = "flight_number",nullable = false)
    private Long flightNumber;

    @Column(name = "flight_date",nullable = false)
    private Date flightDate;

    @Column(name = "available_seats",nullable = false)
    private Integer availableSeats;

    @Column(name = "version")
    @Version
    private int version;

    // Getter and Setter

我为50个用户添加了一个单元测试,并使用了ockMvc对功能进行了测试。

@Test
    void bookFlight_Concurrent_Success() throws Exception {
        // Get all flight by Default flight number
        List<Flight> availableFlights = flightService.getAllAvailableFlightsByFlightNumber(defaultFlights.get(0));
        int flightIndex = 3;
        int totalAvailableSeat = availableFlights.get(flightIndex).getAvailableSeats();

        String requestJSON = "{\n" +
                "  \"flightDate\": \"" + availableFlights.get(flightIndex).getFlightDate().toString() + "\",\n" +
                "  \"flightNumber\": " + availableFlights.get(flightIndex).getFlightNumber() + "\n" +
                "}";

        // Set up builders
        List<RequestBuilder> builders = new ArrayList<>();
        for (int i = 0; i < defaultCustomerUsernames.size(); i++){
            String jwt = getJWTByUsername(defaultCustomerUsernames.get(i),constants.CUSTOMER_USER_PASSWORD_0);
            RequestBuilder builder = post("/bookFlight").header("Authorization","Bearer " + jwt).
                    accept(MediaType.APPLICATION_JSON).content(requestJSON).contentType(MediaType.APPLICATION_JSON);
            builders.add(builder);
        }

        // Start threads to book flight
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < defaultCustomerUsernames.size(); i++){
            int finalI0 = i;
            int finalI1 = i;
            Thread thread = new Thread(){
                public void run() {
                    try {
                        System.out.println(this.getName() + ": Start thread " + finalI1);
                        MvcResult result = mockMvc.perform(builders.get(finalI0)).andReturn();
                        System.out.println(this.getName() + ": Finish thread " + finalI1);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };


            thread.start();
            threadList.add(thread);
        }

        try{
            for (Thread thread: threadList){
                thread.join();
            }
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }

        // Verify the left ticket + booked ticket is full amount
        int bookedTicketNum = ticketRepository.findTicketsByFlightId(availableFlights.get(flightIndex).getFlightId()).size();
        int newAvailableTicket = flightRepository.findFlightByFlightId(availableFlights.get(flightIndex).getFlightId()).getAvailableSeats();
        System.out.println("Booked Ticket Num: " + bookedTicketNum);
        System.out.println("New Available Ticket: " + newAvailableTicket);
        assertEquals(totalAvailableSeat,bookedTicketNum+newAvailableTicket);
    }

beforeAllafterAll用于生成默认的航班和客户用户。 测试通过了,但是

2020-09-22 21:34:00:667 ERROR [Thread-7] org.hibernate.engine.jdbc.batch.internal.BatchingBatch [130] : HHH000315: Exception executing batch [java.sql.BatchUpdateException: Deadlock found when trying to get lock; try restarting transaction],SQL: update flight set available_seats=?,flight_date=?,flight_number=?,version=? where flight_id=? and version=?
2020-09-22 21:34:00:668 ERROR [Thread-7] org.hibernate.engine.jdbc.spi.SqlExceptionHelper [142] : Deadlock found when trying to get lock; try restarting transaction

我使用 SHOW ENGINE INNODB STATUS来检查死锁日志并得到:


=====================================
2020-09-22 21:36:50 0x700001f89000 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 17 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 1595 srv_active,0 srv_shutdown,22112 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 2965
OS WAIT ARRAY INFO: signal count 44793
RW-shared spins 164384,rounds 166427,OS waits 1899
RW-excl spins 63944,rounds 93238,OS waits 183
RW-sx spins 28152,rounds 88279,OS waits 316
Spin rounds per wait: 1.01 RW-shared,1.46 RW-excl,3.14 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-09-22 21:34:00 0x70000156e000
*** (1) TRANSACTION:
TRANSACTION 34961,ACTIVE 0 sec starting index read
mysql tables in use 1,locked 1
LOCK WAIT 7 lock struct(s),heap size 1136,3 row lock(s),undo log entries 1
MySQL thread id 586,OS thread handle 123145342349312,query id 3508200 localhost 127.0.0.1 root updating
update flight set available_seats=149,flight_date='2020-12-14',flight_number=2,version=7 where flight_id=178360 and version=6

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 34961 lock mode S locks rec but not gap
Record lock,heap no 85 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002b8b8; asc     ;;
 1: len 6; hex 000000008889; asc       ;;
 2: len 7; hex 02000002091880; asc        ;;
 3: len 4; hex 80000002; asc     ;;
 4: len 3; hex 8fc98e; asc    ;;
 5: len 4; hex 80000095; asc     ;;
 6: len 4; hex 80000007; asc     ;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 34961 lock_mode X locks rec but not gap waiting
Record lock,heap no 85 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002b8b8; asc     ;;
 1: len 6; hex 000000008889; asc       ;;
 2: len 7; hex 02000002091880; asc        ;;
 3: len 4; hex 80000002; asc     ;;
 4: len 3; hex 8fc98e; asc    ;;
 5: len 4; hex 80000095; asc     ;;
 6: len 4; hex 80000007; asc     ;;


*** (2) TRANSACTION:
TRANSACTION 34959,undo log entries 1
MySQL thread id 591,OS thread handle 123145335984128,query id 3508204 localhost 127.0.0.1 root updating
update flight set available_seats=149,version=7 where flight_id=178360 and version=6

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 34959 lock mode S locks rec but not gap
Record lock,heap no 85 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002b8b8; asc     ;;
 1: len 6; hex 000000008889; asc       ;;
 2: len 7; hex 02000002091880; asc        ;;
 3: len 4; hex 80000002; asc     ;;
 4: len 3; hex 8fc98e; asc    ;;
 5: len 4; hex 80000095; asc     ;;
 6: len 4; hex 80000007; asc     ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 34959 lock_mode X locks rec but not gap waiting
Record lock,heap no 85 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002b8b8; asc     ;;
 1: len 6; hex 000000008889; asc       ;;
 2: len 7; hex 02000002091880; asc        ;;
 3: len 4; hex 80000002; asc     ;;
 4: len 3; hex 8fc98e; asc    ;;
 5: len 4; hex 80000095; asc     ;;
 6: len 4; hex 80000007; asc     ;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 35089
Purge done for trx's n:o < 35089 undo n:o < 0 state: running but idle
History list length 0
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 422038541902072,not started
0 lock struct(s),0 row lock(s)
---TRANSACTION 422038541902912,0 row lock(s)
---TRANSACTION 422038541901232,0 row lock(s)
---TRANSACTION 422038541897872,0 row lock(s)
---TRANSACTION 422038541899552,0 row lock(s)
---TRANSACTION 422038541898712,0 row lock(s)
---TRANSACTION 422038541897032,0 row lock(s)
---TRANSACTION 422038541896192,0 row lock(s)
---TRANSACTION 422038541895352,0 row lock(s)
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: [0,0],aio writes: [0,ibuf aio reads:,log i/o's:,sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 1
1276 OS file reads,982454 OS file writes,737797 OS fsyncs
0.00 reads/s,0 avg bytes/read,0.00 writes/s,0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1,free list len 0,seg size 2,0 merges
merged operations:
 insert 0,delete mark 0,delete 0
discarded operations:
 insert 0,delete 0
Hash table size 34679,node heap has 1 buffer(s)
Hash table size 34679,node heap has 2 buffer(s)
Hash table size 34679,node heap has 4 buffer(s)
0.00 hash searches/s,0.00 non-hash searches/s
---
LOG
---
Log sequence number          1793856320
Log buffer assigned up to    1793856320
Log buffer completed up to   1793856320
Log written up to            1793856320
Log flushed up to            1793856320
Added dirty pages up to      1793856320
Pages flushed up to          1793856320
Last checkpoint at           1793856320
937447 log i/o's done,0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137363456
Dictionary memory allocated 476872
Buffer pool size   8191
Free buffers       5484
Database pages     2694
Old database pages 982
Modified db pages  0
Pending reads      0
Pending writes: LRU 0,flush list 0,single page 0
Pages made young 4,not young 0
0.00 youngs/s,0.00 non-youngs/s
Pages read 1241,created 1453,written 34817
0.00 reads/s,0.00 creates/s,0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s,evicted without access 0.00/s,Random read ahead 0.00/s
LRU len: 2694,unzip_LRU len: 0
I/O sum[0]:cur[0],unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB,0 queries in queue
0 read views open inside InnoDB
Process ID=2483,Main thread ID=0x70000177a000,state=sleeping
Number of rows inserted 2692764,updated 1168,deleted 1168363,read 1256843
0.00 inserts/s,0.00 updates/s,0.00 deletes/s,0.00 reads/s
Number of system rows inserted 22,updated 377,deleted 20,read 13121
0.00 inserts/s,0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

我认为两次更新交易会导致此问题。 我尝试进行手动调试,但仍然可以使用其他工具在Flight newFlight = flightRepository.save(returnedFlight);之后更新行 但是,我无法更新Ticket returnedTicket = ticketRepository.save(newTicket);

之后的行

死锁日志:


=====================================
2020-09-22 21:41:23 0x700001f89000 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 29 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 1597 srv_active,22382 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 2965
OS WAIT ARRAY INFO: signal count 44793
RW-shared spins 164384,3.14 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-09-22 21:41:17 0x70000156e000
*** (1) TRANSACTION:
TRANSACTION 35092,ACTIVE 24 sec starting index read
mysql tables in use 1,locked 1
LOCK WAIT 2 lock struct(s),1 row lock(s)
MySQL thread id 8,OS thread handle 123145333862400,query id 3508825 localhost 127.0.0.1 root updating
/* ApplicationName=DataGrip 2020.2.2 */ UPDATE airline_booking_system.flight t SET t.version = 12 WHERE t.flight_id = 173431

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 35092 lock_mode X locks rec but not gap waiting
Record lock,heap no 4 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002a577; asc    w;;
 1: len 6; hex 000000008911; asc       ;;
 2: len 7; hex 020000016c0c77; asc     l w;;
 3: len 4; hex 80000373; asc    s;;
 4: len 3; hex 8fc938; asc   8;;
 5: len 4; hex 80000096; asc     ;;
 6: len 4; hex 8000000b; asc     ;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 35092 lock_mode X locks rec but not gap waiting
Record lock,heap no 4 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002a577; asc    w;;
 1: len 6; hex 000000008911; asc       ;;
 2: len 7; hex 020000016c0c77; asc     l w;;
 3: len 4; hex 80000373; asc    s;;
 4: len 3; hex 8fc938; asc   8;;
 5: len 4; hex 80000096; asc     ;;
 6: len 4; hex 8000000b; asc     ;;


*** (2) TRANSACTION:
TRANSACTION 35091,ACTIVE 41 sec starting index read
mysql tables in use 1,undo log entries 1
MySQL thread id 599,OS thread handle 123145336287232,query id 3508827 localhost 127.0.0.1 root updating
update flight set available_seats=149,flight_date='2020-09-24',flight_number=883,version=11 where flight_id=173431 and version=10

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 35091 lock mode S locks rec but not gap
Record lock,heap no 4 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002a577; asc    w;;
 1: len 6; hex 000000008911; asc       ;;
 2: len 7; hex 020000016c0c77; asc     l w;;
 3: len 4; hex 80000373; asc    s;;
 4: len 3; hex 8fc938; asc   8;;
 5: len 4; hex 80000096; asc     ;;
 6: len 4; hex 8000000b; asc     ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 17 page no 4 n bits 480 index PRIMARY of table `airline_booking_system`.`flight` trx id 35091 lock_mode X locks rec but not gap waiting
Record lock,heap no 4 PHYSICAL RECORD: n_fields 7; compact format; info bits 128
 0: len 4; hex 8002a577; asc    w;;
 1: len 6; hex 000000008911; asc       ;;
 2: len 7; hex 020000016c0c77; asc     l w;;
 3: len 4; hex 80000373; asc    s;;
 4: len 3; hex 8fc938; asc   8;;
 5: len 4; hex 80000096; asc     ;;
 6: len 4; hex 8000000b; asc     ;;

*** WE ROLL BACK TRANSACTION (1)
------------
TRANSACTIONS
------------
Trx id counter 35093
Purge done for trx's n:o < 35091 undo n:o < 0 state: running but idle
History list length 1
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 422038541900392,0 row lock(s)
---TRANSACTION 422038541902072,982489 OS file writes,737827 OS fsyncs
0.00 reads/s,0.24 writes/s,0.17 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1,node heap has 4 buffer(s)
0.03 hash searches/s,0.10 non-hash searches/s
---
LOG
---
Log sequence number          1793857089
Log buffer assigned up to    1793857089
Log buffer completed up to   1793857089
Log written up to            1793857089
Log flushed up to            1793857089
Added dirty pages up to      1793857089
Pages flushed up to          1793857089
Last checkpoint at           1793857089
937456 log i/o's done,0.03 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137363456
Dictionary memory allocated 476872
Buffer pool size   8191
Free buffers       5484
Database pages     2694
Old database pages 982
Modified db pages  0
Pending reads      0
Pending writes: LRU 0,written 34832
0.00 reads/s,0.00 writes/s
Buffer pool hit rate 1000 / 1000,young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s,state=sleeping
Number of rows inserted 2692765,updated 1169,read 1256855
0.00 inserts/s,0.03 reads/s
Number of system rows inserted 22,read 13205
0.00 inserts/s,0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

如果有人可以帮助我?我尝试过悲观锁,没问题。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...