Database 数据库记录处理
如何在多个线程之间分配记录的工作负载?例如,Accuracer DB有169条记录,有7个线程 因为我可以把记录的数量分成几个范围,让每个线程处理这个范围。但如果用户删除或添加记录,它将无法正常工作。您可以使用它并行处理数据库中的记录,而无需太多麻烦 我使用管道抽象编写了一个示例。管道施工分为三个阶段:Database 数据库记录处理,database,delphi,delphi-xe2,accuracerdb,Database,Delphi,Delphi Xe2,Accuracerdb,如何在多个线程之间分配记录的工作负载?例如,Accuracer DB有169条记录,有7个线程 因为我可以把记录的数量分成几个范围,让每个线程处理这个范围。但如果用户删除或添加记录,它将无法正常工作。您可以使用它并行处理数据库中的记录,而无需太多麻烦 我使用管道抽象编写了一个示例。管道施工分为三个阶段: 第一阶段从数据库读取数据,创建容器对象的实例,以表示管道下一阶段的数据 第二阶段处理传入的数据 使用过程调用DoSomethingWith,该过程只会浪费大约100毫秒的时间来模拟数据的处理
- 使用过程调用
DoSomethingWith,该过程只会浪费大约100毫秒的时间来模拟数据的处理
- 释放容器实例的内存李>
- 然后将文本值
添加到输出队列,以通知最后一个阶段已处理另一条记录李>1
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
OtlCommon,
OtlCollections,
OtlParallel,
System.Diagnostics,
DB, DBClient;
type
//auxiliar container, used to copy the database data
//to avoid synchronization. remember TDataSet "current record"
//may cause conflicts if changed from different threads.
TContainer = class
private
FName: string;
FID: Int64;
public
property ID: Int64 read FID write FID;
property Name: string read FName write FName;
end;
//does nothing, but wastes around 100ms. "processing" each record
procedure DoSomethingWith(const AValue: TContainer);
begin
Sleep(100);
end;
//creates a DataSet on the fly with a random number of records
function CreateDataSet: TClientDataSet;
var
I: Integer;
begin
Result := TClientDataSet.Create(nil);
with Result.FieldDefs.AddFieldDef do
begin
Name := 'ID';
DataType := ftLargeint;
end;
with Result.FieldDefs.AddFieldDef do
begin
Name := 'NAME';
DataType := ftString;
end;
Result.CreateDataSet;
for I := 1 to Random(1000) do
Result.InsertRecord([I, 'Test']);
end;
var
RecordsProcessed: Integer;
SW: TStopwatch;
Data: TDataSet;
begin
IsMultiThread := True;
Randomize;
Writeln('wait while processing...');
SW := TStopwatch.Create;
SW.Start;
try
Data := CreateDataSet;
try
RecordsProcessed := Parallel.Pipeline
.Stage(
procedure (const Input, Output: IOmniBlockingCollection)
var
RecData: TContainer;
begin
Data.First;
while not Data.Eof do
begin
RecData := TContainer.Create;
RecData.ID := Data.Fields[0].AsLargeInt;
RecData.Name := Data.Fields[1].AsString;
Output.Add(RecData);
Data.Next;
end;
end)
.Stage(
procedure (const Input: TOmniValue; var Output: TOmniValue)
begin
//process the real thing here
DoSomethingWith(Input);
Input.AsObject.Free;
Output := 1; //another record
end)
.NumTasks(7) //this stage is processed by 7 parallel tasks
.Stage(
procedure (const Input, Output: IOmniBlockingCollection)
var
Recs: Integer;
Value: TOmniValue;
begin
Recs := 0;
for Value in Input do
Inc(Recs, Value);
Output.Add(Recs);
end)
.Run.Output.Next;
SW.Stop;
Writeln(RecordsProcessed, ' records processed in ', SW.ElapsedMilliseconds, 'ms.');
Writeln('Avg. ', (SW.ElapsedMilliseconds/RecordsProcessed):0:3, 'ms./record');
finally
Data.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
IMHO,这样做的主要优点是:
- 您有一个灵活的机制在多个工作人员之间分配作业。如果某些记录需要更多的时间来处理,那么库会处理好这种情况,您可以合理地期望在更短的时间内完成全部工作
- 从数据库中读取完第一条记录后,第一个处理线程就开始了
- 如果必须等待基表中更多的传入记录,则可以轻松地进行调整。在阶段过程中的代码结束之前,阶段的输出队列不会标记为已完成。如果在某个时候没有更多的工作要做,所有即将到来的阶段都会阻塞等待更多的数据处理
- 您只需更改参数值即可更改工作线程的数量李>
Run
方法会立即返回。数据是直接从数据集读取还是复制的?嘿,这里有(注释的)代码可以查看它!。你为什么问这样的问题?你理解代码吗?你读过注释了吗?