在OnExecute事件中使用数据库(Indy)

时间:2010-08-21 10:07:56

标签: multithreading delphi indy dbconnection

我有一个包含以下代码的服务器:

procedure TFrmMain.TCPServerExecute(AContext: TIdContext);
begin
      Res := DoRegister(Name,Family,Username,Password);
end;

function TFrmMain.DoRegister(Name,Family,Username,Password:string): bool;
var
  Qry: TSQLQuery;
begin
  Qry := TSQLQuery.Create(nil);
  try
    Qry.SQLConnection := FrmConnect.SQLConnection;
    Qry.SQL.Text :='INSERT INTO `table` ...';
    Qry.ExecSQL();
  finally
    Qry.Free;
  end;
  Result := True;
end;

在各种线程中访问一个表有什么问题吗?并且完全在Onexecute事件中使用了什么危险?

感谢您回复朋友。

那么,这是为不同线程建立不同连接的真正方法吗?

var
  Qry: TSQLQuery;
  SqlCon: TSQLConnection;
Begin
  SqlCon := TSQLConnection.Create(nil);
  Qry := TSQLQuery.Create(nil);
  try
    SqlCon := FrmConnect.SQLConnection;
    Qry.SQLConnection := SqlCon;
  finally
    SqlCon.Free;
    Qry.Free;
  end;
end;

4 个答案:

答案 0 :(得分:2)

是和否。您可以从不同的线程访问单个表,但每个线程需要一个TSQLConnection实例才能安全地执行此操作。

<强>更新

为每个线程实例化不同的连接很好。这也是大多数网页一直在做的事情(使用asp,php或...的服务器端脚本意味着无状态执行,因此连接通常不能存活到下一个请求并且必须重新建立)。

如果您担心开销,可以考虑使用vcldeveloper建议的单一连接。您必须确保由其他线程更改的“连接线程”使用的任何变量和成员字段(例如,接收要执行的SQL的字段成员)将必须受某种同步机制的保护

同样适用于mjustin建议的连接池,但在这种情况下,连接池需要通过同步机制进行保护。

答案 1 :(得分:2)

访问DB的每个线程都应该有自己的连接,不能在多个线程之间共享数据库连接。 OnExecute事件在与请求客户端对应的线程的上下文中调用,因此每次调用它时,它都在工作线程内执行,并且这样的线程应该有自己的数据库连接。

如果您不想为每个工作线程建立新连接;一个选项可能是,您将单个线程专用于数据库连接,并将所有数据库操作委托给该线程,例如,您的其他线程可以将其INSERT SQL语句发送到该数据库线程中的队列,并且该数据库线程执行它们 - by-one使用单个DB连接。当然,如果你采用这种方法,所有数据库负载都将在一个线程上,如果你有这么多的数据库操作,那么这个数据库线程本身可能是一个性能瓶颈!更重要的是,采用这种方法,查询执行将是异步的,除非您的每个线程都要求数据库线程为它们执行数据库查询时使用同步技术。

另请注意,如果您的数据库访问组件是ADO,那么您必须调用CoInitialize和CoUninitialize,因为Delphi运行时只对主线程执行此操作而不是由您创建的其他线程。

答案 2 :(得分:2)

我会使用连接池进行数据库连接。然后,每个线程只在需要时请求来自池的连接(如果池中当前没有空闲连接,则可能会阻塞),然后使用并最终将其返回到池中。池的优点是所需的连接少于并发线程,并且在需要时连接已经存在。

答案 3 :(得分:2)

您的第二个代码片段不正确。当您应该复制连接字符串时,您将使用全局连接覆盖新连接。您还可以释放全局,这可能会导致其他应用程序出现问题。这样的事情,取决于你的TSQLConnection类的细节:

SqlCon := TSQLConnection.Create(nil); // create
Qry := TSQLQuery.Create(nil);
try
  //SqlCon := FrmConnect.SQLConnection; // overwrite!!!
  SqlCon.ConnectionString :=  FrmConnect.SQLConnection.ConnectionString;
  SqlCon.Active := true;
  Qry.SQLConnection := SqlCon;
  ...

如果你想拥有数据库连接池,那么它非常棘手,因为连接通常是特定于线程的 - 你需要每个线程一个,你不能在线程之间传递它们。所以你最终写了很多代码来支持它。

我现在使用OmniThreadLibrary并拥有一个返回新数据库连接的工厂方法。这给了我一个线程池,我将任务提供给,所以我的特定任务在执行时绑定到现有线程,但线程是相当长寿的。为了得到这个,我必须编写的代码非常小(我正在使用ADO):

type
    // a factory to generate new instances of our thread-specific data
    IThreadPoolData = interface
        ['{14917B01-6613-4737-B87E-0046789D4284}']
        function GetConnection: TADOConnection;
        function GetStoredProc: TADOStoredProc;
    end;

    TThreadPoolData = class(TInterfacedObject, IThreadPoolData)
    strict private
        FADOConnection: TADOConnection;
        FStoredProc: TADOStoredProc; // lazy creation!
    public
        constructor Create(aConnectionString: string); overload;
        destructor Destroy; override;
        function GetConnection: TADOConnection;
        function GetStoredProc: TADOStoredProc;
    end;

// create the connection here so thread creation is slow but using it 
// is (relatively) fast

constructor TThreadPoolData.Create(aConnectionString: string);
begin
    FADOConnection := TADOConnection.Create(nil);
    FADOConnection.LoginPrompt := false;
    FADOConnection.ConnectionString := aConnectionString;
    FADOConnection.ConnectOptions := coAsyncConnect;
    FADOConnection.Connected := true;
end;

destructor TThreadPoolData.Destroy;
begin
    FADOConnection.Connected := false;
    if assigned(FStoredProc) then
        FreeAndNil(FStoredProc);
    FreeAndNil(FADOConnection);
end;

如果您编写自己的线程或连接池,则需要执行类似的操作。