简体   繁体   English

如何对数据库原子进行多次更新和查询操作?

[英]How to make several update and query operations to the database atomic?

I am building a pet project (delivery service - customers and couriers) using plain Servlets and JDBC.我正在使用普通 Servlet 和 JDBC 构建一个宠物项目(交付服务 - 客户和快递员)。 When the customer sends new order form via JSP, I need to analyze data, create new order entity, and store it in the database.当客户通过 JSP 发送新订单时,我需要分析数据,创建新的订单实体,并将其存储在数据库中。 I also generate a so called track number per each new or pending order (just a sequence of symbols and numbers), so the customer may search his order and check status.我还为每个新订单或挂单生成一个所谓的跟踪号(只是一系列符号和数字),因此客户可以搜索他的订单并检查状态。 So the create order logic is所以创建顺序逻辑是

  1. Fill in order entity填写订单实体
  2. Download list of all track numbers in use from the DB从数据库下载所有正在使用的曲目编号列表
  3. Keep generating track number until it doesn't match any one in the list继续生成曲目编号,直到它与列表中的任何一个都不匹配
  4. Assign track number to the order entity为订单实体分配跟踪号
  5. Store the order entity in the database将订单实体存储在数据库中
  6. Send confirmation email with track number to customer向客户发送带有跟踪号的确认电子邮件

The thing is that operations 2-5 must be atomic (a request thread was sent to sleep by the scheduler after it has downloaded list of track numbers in use and generated a track number, but before it has stored new order. In the worst case scenario another thread could have generated the same track number and store the order in the database which will lead to errors), and I can't figure out how to do it optimally.问题是操作 2-5 必须是原子的(调度程序在下载使用中的曲目编号列表并生成曲目编号之后,但在存储新订单之前,将请求线程发送到睡眠状态。在最坏的情况下另一个线程可能会生成相同的曲目编号并将订单存储在数据库中,这将导致错误),我无法弄清楚如何以最佳方式执行此操作。

My current solution is to place methods getActiveTracks(), generateTrackNumber() and storeOrder() in the synchronized block using ReentrantLock with timeout.我当前的解决方案是使用带超时的 ReentrantLock 将方法 getActiveTracks()、generateTrackNumber() 和 storeOrder() 放在同步块中。 But I wonder if it will have any negative impact on concurrent requests from multiple users.但是我想知道它是否会对来自多个用户的并发请求产生负面影响。 My second thought was to make it as a transaction at the DAO layer, but I don't know how to manage several operations simultaneously (query the list of track numbers from the orders table, check if a newly generated track number corresponds to any one from the list, and if not, update the orders table with new order)我的第二个想法是在DAO层做一个事务,但是我不知道如何同时管理几个操作(从orders表中查询轨道号列表,检查新生成的轨道号是否对应任何一个从列表中,如果没有,用新订单更新订单表)

Edit I should have indicated that I'm not using track numbers as primary keys and I do have unique ids to each order record (just a BIGSERIAL type).编辑我应该指出我没有使用跟踪号作为主键,而且我对每个订单记录都有唯一的 ID(只是一个 BIGSERIAL 类型)。 The track number is more of a convenience for 'customer' (its 4-symbol long, eg X5F4, which customer can easily memorize and enter into the search bar on the website).曲目编号更方便“客户”(其4个符号长,例如X5F4,客户可以轻松记住并输入网站的搜索栏)。 These track numbers may repeat many times since the system can distinguish each record by order id and status (COMPLETED, NEW, PENDING).这些跟踪编号可能会重复多次,因为系统可以通过订单 ID 和状态(已完成、新建、待处理)区分每条记录。 So in my situation I generate track number until it doesn't match the one with NEW or PENDING (it doesn't matter if it matches COMPLETED).因此,在我的情况下,我会生成曲目编号,直到它与 NEW 或 PENDING 的曲目不匹配(它是否与 COMPLETED 匹配并不重要)。

So each of my order rows are like所以我的每个订单行都像

id ID track_number追踪号码 status地位 date日期
3847 3847 X45G X45G NEW新的 2021-07-19 2021-07-19
4657 4657 X45G X45G COMPLETED完全的 2021-05-03 2021-05-03

I need to perform following operations when creating new order创建新订单时我需要执行以下操作

  1. Query DB for the list of all new or pending orders SELECT track_number FROM orders WHERE status IN('NEW', 'PENDING')查询数据库以获取所有新订单或挂单的列表SELECT track_number FROM orders WHERE status IN('NEW', 'PENDING')
  2. Generate track number until it doesn't match any one in the list生成曲目编号,直到它与列表中的任何一个都不匹配
  3. Update DB INSERT INTO orders VALUES(DEFAULT, ?, ?, ?)更新 DB INSERT INTO orders VALUES(DEFAULT, ?, ?, ?)

And I need these three operations to be atomic我需要这三个操作是原子的

Two main things:主要有两点:

  1. Realize that even with synchronized blocks if your application ever moves into a clustered environment you will very likely create collisions in a busy environment when generating the track number.意识到即使使用synchronized块,如果您的应用程序移动到集群环境中,您在生成轨道编号时很可能会在繁忙的环境中产生冲突。
  2. Obtaining the track number this way will do nothing but slow down over time as the list grows.随着列表的增长,以这种方式获取曲目编号只会随着时间的推移而减慢。

Instead, I'd start with using a GUID for each "track number".相反,我首先为每个“曲目编号”使用一个 GUID。 A simple:一个简单的:

import java.util.UUID;


String uuid = UUID.randomUUID().toString();

yourOrderObject.setId(uuid);

No transactions needed.不需要交易。

Second, you should think of storing in the database and sending an email as separate tasks.其次,您应该将存储在数据库中和发送电子邮件视为单独的任务。 I would not want to roll back the transaction if the email couldn't be sent for some reason.如果由于某种原因无法发送电子邮件,我不想回滚交易。

Doing things this way means that you eliminate steps 2 and 3 and likely speed up the system considerably.以这种方式进行操作意味着您可以省去第 2 步和第 3 步,并可能大大加快系统速度。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM