最近发现很多人在做搜索功能的时候都喜欢实现随着输入更新搜索结果
当然这样比以前的那种都输入完了点搜索按钮要直观的多, 同时也省了一次点击, 减少用户操作, 但是带来的一个问题就是不断的建立新的搜索导致性能的降低
比如下面这种代码是最常见的:在数据量小, 数据库连接速度快的时候这样当然没什么问题, 但是一旦反应速度慢了, 就会造成很不好的使用感觉, 每输入一个字符都要有一些卡顿的感觉
procedure TForm1.edtSearchChange(Sender: TObject); begin nQuery.Close; nQuery.SQL.Text := 'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%'); nQuery.Open; {显示搜索结果} end;
现在可以优化一下这个方法: 并不是每个字符都需要等待结果的, 更多的时候用户都是知道几个确认的字符, 都输入以后才想看到搜索结果
根据这个思考一下, 一般输入的时候对于自己确定要输入的内容都比较快, 而输入完毕或者不确定的内容都需要思考一下, 输入速度就慢, 所以我们使用TTimer根据输入速度设置一个查询间隔
tmSearch: TTimer; procedure TfrmSearchTest.FormCreate(Sender: TObject); begin with tmSearch do begin Enabled := False; Interval := 500; end; end; procedure TfrmSearchTest.edtSearchChange(Sender: TObject); begin {每次内容变更都重新计算0.5秒以后开始查询, 这样就让连续输入的内容之间可以不再执行查询} with tmSearch do begin Enabled := True; OnTimer := OnTimer; {这样是为了执行UpdateTimer更新触发事件, 不明白的看OnTimer属性源码} end; end; procedure TfrmSearchTest.tmSearchTimer(Sender: TObject); begin tmSearch.Enabled := False; nQuery.Close; nQuery.SQL.Text := 'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%'); nQuery.Open; {显示搜索结果} end;
OK, 现在用起来的感觉就比较好了但是一旦数据量大了以后, 执行SQL的速度会比较慢, 特别是用了like '%xxx%'这样的东西, 执行速度慢会造成查询时的卡顿
所以, 我们再次改造: 吧SQL放到线程里去执行
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, DB, ADODB, StdCtrls, ExtCtrls, Grids, DBGrids, ActiveX; const WM_USER_SEARCHED = WM_USER + $A0; {定义一个查询完毕通知消息} type TTSearch = class(TThread) FSQL: string; FConStr: string; FNotifyHandle: THandle; protected procedure Execute; override; public destructor Destroy; override; constructor Create(ASQL, AConStr: string; ANotifyHandle: THandle); end; TfrmSearchTest = class(TForm) edtSearch: TEdit; nQuery: TADOQuery; tmSearch: TTimer; DBGrid1: TDBGrid; DataSource1: TDataSource; procedure FormCreate(Sender: TObject); procedure edtSearchChange(Sender: TObject); procedure tmSearchTimer(Sender: TObject); private { Private declarations } procedure WMUSERSEARCHED(var Msg: TMessage); message WM_USER_SEARCHED; public { Public declarations } end; var frmSearchTest: TfrmSearchTest; implementation {$R *.dfm} procedure TfrmSearchTest.FormCreate(Sender: TObject); begin with tmSearch do begin Enabled := False; Interval := 500; end; end; procedure TfrmSearchTest.edtSearchChange(Sender: TObject); begin {每次内容变更都重新计算0.5秒以后开始查询, 这样就让连续输入的内容之间可以不再执行查询} with tmSearch do begin Enabled := True; OnTimer := OnTimer; {这样是为了执行UpdateTimer更新触发事件, 不明白的看OnTimer属性源码} end; end; procedure TfrmSearchTest.tmSearchTimer(Sender: TObject); var nSQL: string; begin tmSearch.Enabled := False; nSQL := 'SELECT XXX FROM TABLE WHERE Field like ' + QuotedStr('%' + edtSearch.Text + '%'); TTSearch.Create(nSQL, nQuery.ConnectionString, Handle); end; { TTSearch } constructor TTSearch.Create(ASQL, AConStr: string; ANotifyHandle: THandle); begin FSQL := ASQL; FConStr := AConStr; FNotifyHandle := ANotifyHandle; FreeOnTerminate := True; inherited Create(False); end; destructor TTSearch.Destroy; begin inherited; end; procedure TTSearch.Execute; var nQ: TADOQuery; begin inherited; CoInitialize(nil); nQ := TADOQuery.Create(nil); try nQ.ConnectionString := FConStr; nQ.SQL.Text := FSQL; nQ.Open; {通过消息吧执行结果发给主进程} nQ.Recordset._AddRef; {手动增加接口引用计数, 防止nQ.Free的时候接口被一起释放} {Integer(nQ.Recordset) 这样调用并不会增加接口的引用计数} PostMessage(FNotifyHandle, WM_USER_SEARCHED, 0, Integer(nQ.Recordset)); finally nQ.Free; end; CoUninitialize; end; procedure TfrmSearchTest.WMUSERSEARCHED(var Msg: TMessage); begin with nQuery do begin Close; Recordset := _Recordset(Msg.LParam); Recordset._Release; {减少一个引用计数, 防止接口无法释放造成内存泄漏} {显示搜索结果} end; end; end.
恩, 现在不卡顿了, 不过再仔细想想还有没有什么可以优化的地方(职业病又犯了)
1. 每次查询都要从数据库中select, 这样会不会太浪费资源了
2. 每次查询, 如果只是新输入的字母 那肯定是在原有已查到的信息中做二次查询, 而不可能包含新的查询结果
恩 所以只有在搜索内容变短或者第一次输入的时候才做数据库查询, 其余的在本地做内存查询就够了
代码就不帖了, 有点多, 各位看官要自己动脑喽
{------------------------------------无敌分隔线--------------------------------------}
下一步优化:
可以增加一个数据库查询的阀值, 即大概在多少个字符或者多详细的关键词以内, 使用本地内存数据搜索, 其外使用数据库搜索
如果关键词太笼统, 返回的数据太庞大, 占用内存太多, 关键词太详细, 返回的数据太少, 下一步内存搜索基本没什么用
另外, 内存搜索也不是每次都循环数据库返回的数据, 而是在上一次搜索结果下再进行搜索, 同样只有不包含上一次的搜索内容时才开始从返回的数据库值进行搜索
还有, 内存搜索可以灵活排序
另外, 个人推荐别用数据感知控件, 使用listview的ownerdata模式, 显示速度会更快, 同样对数据显示的掌控能力也更强