As folks in the comments have said, you're likely to hit limit issues if the volume is sufficiently high. There are two solutions I've used for this. The first solution sounds close to what you're trying to do, so I'll present it first. The second I've found to be more reliable, but it may take some refactoring on your part.
First solution is to use a Queueable instead of a batch job. Queueables allow you to pass arbitrary data from one asynchronous job to another, as opposed to a flat list of records. So what you can do is create a Queueable that holds everything that you want to process, and only do as much as you want each time it runs.
@AuraEnabled
public static string batchChangeItemStatus(Map<String, Map<String, Object>> filters, String status){
List<Item__c> items = getSearchedItems(filters);
System.debug(items);
List<String> sellerIds = getSellerIds(items);
System.debug('sellerIdBatch ====>' + sellerIds[0]);
QueueableApexListItems q = new QueueableApexListItems(getCsvItems(items, status), sellerIds);
return System.enqueueJob(q);
}
public class QueueableApexListItems implements Queueable {
public List<Item__c> csvItems { get; private set; }
public List<String> sellerIds { get; private set; }
public Integer sellerIdx { get; private set; }
public Integer itemIdx { get; private set; }
public QueueableApexListItems(List<Item__c> csvItems, List<String> sellerIds) {
this.csvItems = csvItems;
this.sellerIds = sellerIds;
this.sellerIdx = 0;
this.itemIdx = 0;
}
public void execute(QueueableContext x) {
if (this.sellerIdx == this.sellerIds.size() || csvItems.isEmpty()) return;
Id sellerIdBatch = sellerIds[this.sellerIdx];
// process end of a seller
if (this.itemIdx == this.csvItems.size()) {
// do any final processing
this.itemIdx = 0;
this.sellerIdx++;
System.enqueueJob(this);
}
// process item for seller
else {
Item__c csvItem = csvItems[this.itemIdx];
// perform processing
this.itemIdx++;
System.enqueueJob(this);
}
}
}
The queueable allows you to re-enqueue itself from within its own run. You are able to enqueue exactly one queueable from inside the running context of a queueable, so this allows you to create a chain of asynchronous processes that run one after another. Since you're talking about multiple batch jobs, which themselves would contain multiple records to process, my working assumption in the code above is that you're trying to "loop through" each seller for each item. So I demonstrated how to manage that complexity in a single queueable using two arrays, and two indices.
The second option would be to use platform events. Each platform event would be given a seller id and an item id, and then you'd write a trigger handler to process the event, querying any data needed afresh. Our org has moved more toward this strategy, because we've found platform events to be much more graceful in failure. When a failure occurs (e.g. due to a transient record locking error or whatever), you can catch the error and replay platform events, rather than have it fail silently. Also, if one event fails, it won't necessarily take down every other record combination you're trying to process. Queueables are problematic in that one failure can take down the whole queueable, which will stop everything that was loaded in the queueable to process. We of course catch all our errors, but our org has problems with CPU timeouts, which cannot be caught, so we've moved more toward platform events.
Hope that helps!