写在前面
欢迎交流指教
经测试发现在特殊情况下,事务方法通过依赖注入的方式调用非事务方法(@Transactional(propagation = Propagation.NOT_SUPPORTED)),会造成锁超时。
主流程截图
代码执行异常
2024-06-28T00:54:00.652+08:00 ERROR 3784 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.CannotAcquireLockException:
### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
### The error may exist in com/gyg/demo1/mapper/OrderItemsMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: DELETE FROM order_items WHERE (order_id = ?)
### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction] with root cause
com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:124) ~[mysql-connector-j-8.0.33.jar:8.0.33]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.33.jar:8.0.33]
数据库分析
select * from information_schema.innodb_trx;
SELECT * FROM sys.innodb_lock_waits;
通过innodb_trx可以看到有两个事务,WTF。。。。
1、innodb_trx结果显示 6231是delete方法的事务,6228是整体事务,事务6228锁住了这两条记录,事务6231想要对一行记录加锁,但是和之前的事务部分重合了导致等待锁(不会释放,已经死锁了,事务6228不会提交也不胡回滚,导致会获取锁超时)
{
"trx_id": 6231,
"trx_state": "LOCK WAIT",
"trx_started": "2024-06-27 17:22:32",
"trx_requested_lock_id": "140081923306280:5:4:2:140081834817296",
"trx_wait_started": "2024-06-27 17:22:32",
"trx_weight": 2,
"trx_mysql_thread_id": 173,
"trx_query": "DELETE FROM order_items WHERE (order_id = 1854)",
"trx_operation_state": "starting index read",
"trx_tables_in_use": 1,
"trx_tables_locked": 1,
"trx_lock_structs": 2,
"trx_lock_memory_bytes": 1128,
"trx_rows_locked": 1,
"trx_rows_modified": 0,
"trx_concurrency_tickets": 0,
"trx_isolation_level": "REPEATABLE READ",
"trx_unique_checks": 1,
"trx_foreign_key_checks": 1,
"trx_last_foreign_key_error": null,
"trx_adaptive_hash_latched": 0,
"trx_adaptive_hash_timeout": 0,
"trx_is_read_only": 0,
"trx_autocommit_non_locking": 0,
"trx_schedule_weight": 1
},
{
"trx_id": 6228,
"trx_state": "RUNNING",
"trx_started": "2024-06-27 17:22:32",
"trx_requested_lock_id": null,
"trx_wait_started": null,
"trx_weight": 5,
"trx_mysql_thread_id": 172,
"trx_query": null,
"trx_operation_state": null,
"trx_tables_in_use": 0,
"trx_tables_locked": 2,
"trx_lock_structs": 3,
"trx_lock_memory_bytes": 1128,
"trx_rows_locked": 1,
"trx_rows_modified": 2,
"trx_concurrency_tickets": 0,
"trx_isolation_level": "REPEATABLE READ",
"trx_unique_checks": 1,
"trx_foreign_key_checks": 1,
"trx_last_foreign_key_error": null,
"trx_adaptive_hash_latched": 0,
"trx_adaptive_hash_timeout": 0,
"trx_is_read_only": 0,
"trx_autocommit_non_locking": 0,
"trx_schedule_weight": null
}
2、innodb_lock_waits结果显示blocking_trx_id(阻塞事务的 ID)正是6228
{
"wait_started": "2024-06-27 17:22:32",
"wait_age": "00:00:13",
"wait_age_secs": 13,
"locked_table": "`test`.`order_items`",
"locked_table_schema": "test",
"locked_table_name": "order_items",
"locked_table_partition": null,
"locked_table_subpartition": null,
"locked_index": "PRIMARY",
"locked_type": "RECORD",
"waiting_trx_id": 6231,
"waiting_trx_started": "2024-06-27 17:22:32",
"waiting_trx_age": "00:00:13",
"waiting_trx_rows_locked": 1,
"waiting_trx_rows_modified": 0,
"waiting_pid": 173,
"waiting_query": "DELETE FROM order_items WHERE (order_id = 1854)",
"waiting_lock_id": "140081923306280:5:4:2:140081834817296",
"waiting_lock_mode": "X",
"blocking_trx_id": 6228,
"blocking_pid": 172,
"blocking_query": null,
"blocking_lock_id": "140081923305472:5:4:2:140081834811312",
"blocking_lock_mode": "X,REC_NOT_GAP",
"blocking_trx_started": "2024-06-27 17:22:32",
"blocking_trx_age": "00:00:13",
"blocking_trx_rows_locked": 1,
"blocking_trx_rows_modified": 2,
"sql_kill_blocking_query": "KILL QUERY 172",
"sql_kill_blocking_connection": "KILL 172"
}
innodb_trx 表的字段进行解释
trx_id:事务ID。
trx_state:事务状态,有以下几种状态:RUNNING、LOCK WAIT、ROLLING BACK 和 COMMITTING。
trx_started:事务开始时间。
trx_requested_lock_id:事务当前正在等待锁的标识,可以和 INNODB_LOCKS 表 JOIN 以得到更多详细信息。
trx_wait_started:事务开始等待的时间。
trx_weight:事务的权重。
trx_mysql_thread_id:事务线程 ID,可以和 PROCESSLIST 表 JOIN。
trx_query:事务正在执行的 SQL 语句。
trx_operation_state:事务当前操作状态。
trx_tables_in_use:当前事务执行的 SQL 中使用的表的个数。
trx_tables_locked:当前执行 SQL 的行锁数量。
trx_lock_structs:事务保留的锁数量。
trx_lock_memory_bytes:事务锁住的内存大小,单位为 BYTES。
trx_rows_locked:事务锁住的记录数。包含标记为 DELETED,并且已经保存到磁盘但对事务不可见的行。
trx_rows_modified:事务更改的行数。
trx_concurrency_tickets:事务并发票数。
trx_isolation_level:当前事务的隔离级别。
trx_unique_checks:是否打开唯一性检查的标识。
trx_foreign_key_checks:是否打开外键检查的标识。
trx_last_foreign_key_error:最后一次的外键错误信息。
trx_adaptive_hash_latched:自适应散列索引是否被当前事务锁住的标识。
trx_adaptive_hash_timeout:是否立刻放弃为自适应散列索引搜索 LATCH 的标识。
innodb_lock_waits 表的字段解释
requesting_trx_id:请求事务的 ID。
requested_lock_id:事务所等待的锁定的 ID。可以和 INNODB_LOCKS 表 JOIN。
blocking_trx_id:阻塞事务的 ID。
blocking_lock_id:某一事务的锁的 ID,该事务阻塞了另一事务的运行。可以和 INNODB_LOCKS 表 JOIN。
结论
不要在一个事务没提交又进行其他修改操作,会容易导致获取不到锁
测试代码
测试类
@Transactional(timeout = 30)
@SpringBootTest(classes = Application.class)
class IOrdersServiceTest {
@Autowired
private IOrdersService service;
@Autowired
private IOrderItemsService orderItemsService;
/**
* 新增,再手动删除(业务子表会做分库分表,所有最后手动删除关联数据)
**/
@Test
public void test_transactional() {
Orders orders = generate_orders();
try {
service.save(orders);
OrderItems orderItems = generate_order_items(orders);
orderItemsService.save(orderItems);
}catch (Exception e){
e.printStackTrace();
}finally {
orderItemsService.delete(orders.getOrderId());
}
}
private Orders generate_orders() {
Orders orders = new Orders();
orders.setCustomerId(123456);
orders.setOrderDate(LocalDateTime.now());
orders.setStatus("1");
orders.setTotalAmount(new BigDecimal("100.12"));
orders.setShippingAddress("江西省九江市");
orders.setBillingAddress("浙江省杭州市");
orders.setPaymentMethod("微信");
return orders;
}
private OrderItems generate_order_items(Orders orders) {
OrderItems orderItems = new OrderItems();
orderItems.setOrderId(orders.getOrderId());
orderItems.setProductId(1223);
orderItems.setQuantity(1);
orderItems.setUnitPrice(new BigDecimal("2123.12"));
return orderItems;
}
}
服务
@Service
public class OrderItemsServiceImpl extends ServiceImpl<OrderItemsMapper, OrderItems> implements IOrderItemsService {
@Autowired
private OrderItemsMapper mapper;
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void delete(Integer orderId) {
LambdaQueryWrapper<OrderItems> wrapper = Wrappers.<OrderItems>lambdaQuery().eq(OrderItems::getOrderId, orderId);
mapper.delete(wrapper);
}
}