我的网站使用PHP、MySQL、Smarty、jQuery、AJAX等。目前,我正在从MySQL数据库中获取大量数据(匹配的问题ID),对其进行处理,将这些数据分配给Smarty模板,并将其打印在网页上。由于要获取的数据量太大,并且正在进行进一步处理,因此获取最终输出数据需要花费太多时间。反过来,向用户显示整个数据需要花费太多时间。
我脑海中有一种方法,但无法实现。我的方法是同时运行获取单个匹配的question_id
和将其显示到浏览器的两个过程,并重复这个循环,直到获取并显示所有匹配的问题ID。当显示单行的加载数据时,加载程序图像应该显示在所显示的记录下。打印完所有数据后,加载程序图像应该会消失。
但我面临的主要问题是,当Smarty模板引擎首先加载所有内容,并且只有在完全拥有内容后才将其打印到浏览器时,我应该如何将数据连续分配给Smarty模板并显示模板。
为了供您参考,我将控制器、模型和视图中的所有现有代码放在下面:
Controller(match_question.PHP)的PHP代码如下:
<?php
require_once("../../includes/application-header.php");
$objQuestionMatch = new QuestionMatch();
$request = empty( $_GET ) ? $_POST : $_GET ;
if($request['subject_id']!="")
$subject_id = $request['subject_id'];
if($request['topic_id']!="")
$topic_id = $request['topic_id'];
if($subject_id !='' && $topic_id !='')
$all_match_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id);
$smarty->assign('all_match_questions', $all_match_questions);
$smarty->display("match-question.tpl")
?>
模型(QuestionMatch.PHP)的PHP代码如下:
<?php
class QuestionMatch {
var $mError = "";
var $mCheck;
var $mDb;
var $mValidator;
var $mTopicId;
var $mTableName;
function __construct() {
global $gDb;
global $gFormValidation;
$this->mDb = $gDb;
$this->mValidator = $gFormValidation;
$this->mTableName = TBL_QUESTIONS;
}
/**
* This function is used to get all the questions from the given subject id and topic id
*/
function GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id) {
/*SQL query to find out questions from given subject_id and topic_id*/
$sql = " SELECT * FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id;
$sql .= " AND question_topic_id=".$topic_id;
$this->mDb->Query($sql);
$questions_data = $this->mDb->FetchArray();
/*Same array $questions_data is assigned to new array $questions to avoid the reference mismatching*/
$questions = $questions_data;
/*Array of words to be excluded from comparison process
*For now it's a static array but when UI design will be there the array would be dynamic
*/
$exclude_words = array('which','who','what','how','when','whom','wherever','the','is','a','an','and','of','from');
/*This loop removes all the words of $exclude_words array from all questions and converts all
*converts all questions' text into lower case
*/
foreach($questions as $index=>$arr) {
$questions_array = explode(' ',strtolower($arr['question_text']));
$clean_questions = array_diff($questions_array, $exclude_words);
$questions[$index]['question_text'] = implode(' ',$clean_questions);
}
/*Now the actual comparison of each question with every other question stats here*/
foreach ($questions as $index=>$outer_data) {
/*Logic to find out the no. of count question appeared into tests*/
$sql = " SELECT count(*) as question_appeared_count FROM ".TBL_TESTS_QUESTIONS." WHERE test_que_id=";
$sql .= $outer_data['question_id'];
$this->mDb->Query($sql);
$qcount = $this->mDb->FetchArray(MYSQL_FETCH_SINGLE);
$question_appeared_count = $qcount['question_appeared_count'];
$questions_data[$index]['question_appeared_count'] = $question_appeared_count;
/*Crerated a new key in an array to hold similar question's ids*/
$questions_data[$index]['similar_questions_ids_and_percentage'] = Array();
$outer_question = $outer_data['question_text'];
$qpcnt = 0;
//foreach ($questions as $inner_data) {
/*This foreach loop is for getting every question to compare with outer foreach loop's
question*/
foreach ($questions as $secondIndex=>$inner_data) {
/*This condition is to avoid comparing the same questions again*/
if ($secondIndex <= $index) {
/*This is to avoid comparing the question with itself*/
if ($outer_data['question_id'] != $inner_data['question_id']) {
$inner_question = $inner_data['question_text'];
/*This is to calculate percentage of match between each question with every other question*/
similar_text($outer_question, $inner_question, $percent);
$percentage = number_format((float)$percent, 2, '.', '');
/*If $percentage is >= $percent_match only then push the respective question_id into an array*/
if($percentage >= 85) {
$questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['question_id'] = $inner_data['question_id'];
$questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['percentage'] = $percentage;
/*$questions_data[$secondIndex]['similar_questions_ids_and_percentage'][$qpcnt]['question_id'] = $outer_data['question_id'];
$questions_data[$secondIndex]['similar_questions_ids_and_percentage'][$qpcnt]['percentage'] = $percentage;*/
/*Logic to find out the no. of count question appeared into tests*/
$sql = " SELECT count(*) as question_appeared_count FROM ".TBL_TESTS_QUESTIONS." WHERE test_que_id=";
$sql .= $inner_data['question_id'];
$this->mDb->Query($sql);
$qcount = $this->mDb->FetchArray(MYSQL_FETCH_SINGLE);
$question_appeared_count = $qcount['question_appeared_count'];
$questions_data[$index]['similar_questions_ids_and_percentage'][$qpcnt]['question_appeared_count'] = $question_appeared_count;
$qpcnt++;
}
}
}
}
} //}
/*Logic to create the return_url when user clicks on any of the displayed matching question_ids*/
foreach ($questions_data as $index=>$outer_data) {
if(!empty($outer_data['similar_questions_ids_and_percentage'])) {
$return_url = ADMIN_SITE_URL.'modules/questions/match_question.php?';
$return_url .= 'op=get_question_detail&question_ids='.$outer_data['question_id'];
foreach($outer_data['similar_questions_ids_and_percentage'] as $secondIndex=>$inner_data) {
$return_url = $return_url.','.$inner_data['question_id'];
}
$questions_data[$index]['return_url'] = $return_url.'#searchPopContent';
}
}
/*This will return the complete array with matching question ids*/
return $questions_data;
}
}
?>
View(match-question.tpl)的代码如下:
<table width="100%" class="base-table tbl-practice" cellspacing="0" cellpadding="0" border="0">
<tr class="evenRow">
<th width="33%" style="text-align:center;" class="question-id">Que ID</th>
<th width="33%" style="text-align:center;" class="question-id">Matching Que IDs</th>
<th width="33%" style="text-align:center;" class="question-id">Percentage(%)</th>
</tr>
{if $all_match_questions}
{foreach from=$all_match_questions item=qstn key=key}
{if $qstn.similar_questions_ids_and_percentage}
{assign var=counter value=1}
<tr class="oddRow">
<td class="question-id" align="center" valign="top">
<a href="{$qstn.return_url}" title="View question" class="inline_view_question_detail">QUE{$qstn.question_id}</a>{if $qstn.question_appeared_count gt 0}-Appeared({$qstn.question_appeared_count}){/if}
</td>
{foreach from=$qstn.similar_questions_ids_and_percentage item=question key=q_no}
{if $counter gt 1}
<tr class="oddRow"><td class="question-id" align="center" valign="top"></td>
{/if}
<td class="question" align="center" valign="top">
{if $question.question_id!=''}
<a href="{$qstn.return_url}" title="View question" class="inline_view_question_detail">QUE{$question.question_id}</a>{if $question.question_appeared_count gt 0}-Appeared({$question.question_appeared_count}){/if}
{if $question.question_appeared_count eq 0}
<a id ="{$question.question_id}" href="#" class="c-icn c-remove delete_question" title="Delete question"> Delete</a>{/if}
{/if}
</td>
<td class="question" align="center" valign="top">
{if $question.percentage!=''}{$question.percentage}{/if}
{assign var=counter value=$counter+1}
</td>
</tr>
{/foreach}
{/if}
{/foreach}
{else}
<tr>
<td colspan="2" align="center"><b>No Questions Available</b></td>
</tr>
{/if}
</table>
谢谢你花了一些宝贵的时间来理解我的问题。
我认为瓶颈在于SQL查询的循环。MySQL上有一种对搜索结果进行排名的标准方法。您可以简单地实现全文搜索。
首先,您需要创建一个类似search_results
:的表
SQL:
CREATE TABLE `search_results` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`result_title` varchar(128) CHARACTER SET utf8 NOT NULL,
`result_content` text CHARACTER SET utf8 NOT NULL,
`result_short_description` text CHARACTER SET utf8,
`result_uri` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
`result_resource_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
FULLTEXT KEY `result_title` (`result_title`,`result_content`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
您必须将questions
表格中的所有相关数据(包括问题、主题、答案以及您想在其中搜索的任何内容)插入此处的result_title
和result_content
中(需要更新时也要更新此表格)。还有一个回溯指针指向result_resource_id
上相应表的原始记录。通过一个预定义的URI result_uri
指向您网站中已定义的结果URL,您可以加快一切。您不需要每次都创建一个URL。
现在,您可以在NATURAL LANGUAGE MODE
:中为搜索查询'question?'
创建一个简单的SQL查询
SQL:
SELECT `result_title`, `result_content`, `result_uri`
FROM `search_results` WHERE MATCH(result_title, result_content) AGAINST('question?');
您还可以将相关性度量添加到查询字符串中。还有其他类似布尔值的搜索模式。阅读此处的文档,找到最佳解决方案。
在这些用例中,全文索引更快,也更准确。
通常,模板引擎不会逐段加载内容-您需要手动将数据分块发送到浏览器,并在每个位之间发送flush
。模板库通常在内存中组成整个文档,然后一次性将其转储到浏览器。不过,还是值得查看Smarty手册,以防万一。
作为一种选择,您可以在没有大量数据的情况下呈现页面,然后通过AJAX将其分段加载。虽然10个AJAX连接串行添加了一点额外的开销,但与当前的渲染时间相比,这听起来是最小的。尽管您的总渲染时间可能稍长,但用户感知到的渲染时间会快得多,当然,他们可以看到数据的到来。
我会在domready上启动jQuery中的第一个AJAX操作,当每个操作完成时,它可以启动另一个请求。如果您的服务器可以用JSON而不是HTML进行应答,那么它将允许服务器返回more_available
布尔标志,您可以使用该标志来确定是否需要进行另一次获取。
假设您希望在浏览器中加载内容,而内容仍在从服务器流式传输到客户端,如果您使用表,您可能会遇到浏览器在加载所有数据之前无法呈现表的问题(由于布局问题)。
您可以在相应的部分中看到这些编写快速加载HTML页面的提示,并了解有关表的信息。
一些关键点:
如果浏览器能够立即确定图像和表格的高度和/或宽度,则无需回流内容即可显示网页。这不仅加快了页面的显示速度,还防止了页面加载完成时页面布局中令人讨厌的更改。因此,应尽可能为图像指定高度和宽度。
和:
表格应使用CSS
selector:property
组合:表格布局:固定;
并且应该使用COL和COLGROUP HTML标记来指定列的宽度。
以及:
表仍然被认为是有效的标记,但应该用于显示表格数据。为了帮助浏览器更快地呈现页面,您应该避免嵌套表。
您可能还想研究从PHP流式输出的方法。
有关详细信息,请参阅此问题。
您当前的数据库查询和随后的智能->分配将不允许延迟加载数据以加快进程。
在这种情况下,您可以从查询中识别出可以快速显示给用户的最大行集。一旦确定了可以显示并保持快速响应时间的最大行集,就可以修改查询和模板系统以反映多查询设置。这本质上是分页。您将执行行的初始加载,然后通过jquery加载后一组行,直到成功加载所有"页面"数据,而不是分页。
用于match_question.php
- 首先查询您的数据集,看看您总共有多少行数据
- 将这些行除以在保持快速应用程序的同时可以显示的行总数。这将为您提供将要运行的"页面"或"查询"的总数
- 例如:假设您的测试产生100行作为最佳最快响应。您将对返回2021的预期数据集执行COUNT(*)。您可以将行数除以最佳的100个结果,这将产生20.21或21个"页面",或者在您的情况下,总共21个查询。您的第一个初始查询,以及另外20个ajax查询
-
这将导致对数据库的大量查询,但会使页面加载时间对最终用户更有效。因此,您应该衡量机器的负载与最终用户的易用性。
$limit = 100; $page = 1; ... if($request['page'] != '') $page = $request['page']; ... if($subject_id !='' && $topic_id !=''){ $count_matched_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, true); $page_count = ceil($count/$limit) //round up if decimal for last page; $paged_match_questions = $objQuestionMatch->GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, false, $limit, $page) } $smarty->assign( 'all_match_questions', $paged_match_questions ,'page_count', $page_count); //cache each result page separately to support multiple subject/topic/page combinations to properly utilize the cache mechanism $smarty->display("match-question-".$subject_id."-".$topic_id."-".$page.".tpl")
对于QuestionMatch.php
-
调整查询功能(示例):
function GetSimilarQuestionsBySubjectIdTopicId($subject_id, $topic_id, $count = false, $limit = 0, $page = 0 ) { if($count) { $sql = " SELECT COUNT(*) FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id; } else { $sql = " SELECT * FROM ".TBL_QUESTIONS." WHERE question_subject_id=".$subject_id; } $sql .= " AND question_topic_id=".$topic_id; if($page > 0 && $limit > 0) { $sql .= " LIMIT = " . ($limit*$page)-$limit . ", " . ($limit*$page); } }
查看(匹配问题.tpl)
- 在html元素中输出"pagecount"值,可能是数据页面html5值,并将其分配给具有唯一id的元素
- 页面加载时,让ajax初始化并获取数据页面值
- 使用?通过ajax调用php文档?page=&subject_id=&主题id=
- 循环使用ajax调用,从page=2开始使用数据页面数量,直到查询到最大页面
- 在每次迭代的适当位置附加返回的html
希望这个想法能帮助你找到解决方案。干杯
如果不深入了解代码的具体细节,听起来你想要的是类似于Facebook使用的名为BigPipe的系统,在Facebook工程上的这篇文章中有合理的详细描述。
基本上,他们试图尽快向浏览器发送一条回复,其中包含页面的基本布局,以及稍后将包含加载时间更长的内容的占位符元素——他们称之为pagelet。在刷新初始响应后,依次加载每个小页面,包括从数据库或类似数据库加载数据,并将其发送到客户端——这仍然是同一HTTP请求的一部分。Javascript用于将内容插入到右侧占位符中。
在我工作的公司,我们试验了一段时间,取得了很好的结果。GitHub上有一个开源的第三方PHP/Javascript BigPipe实现,我们将其作为起点。虽然设置起来一点也不琐碎,更重要的是,它能很好地工作,但我相信这正是解决你面临的那种问题的好方法。