首先我们首先要了解几个概念
- MySQL自带英文的全文搜索功能,需要知道fulltext索引和myisam引擎。
- 英文全文搜索是靠单词之间的空格实现的。
- 英文全文搜索的三个模式以及权重分析。
什么是索引
这边我先做一个说一下索引有什么用?众所周知,你要执行查询,如果没有索引的话,他的搜索方式是检表,即从前到后依次对表进行遍历,最简单的例子
SELECT * FROM users WHERE age=18
这条语句会检查所有表中的数据,直到表没数据了为止,可想而知,如果是数据量较少,速度几乎是不能察觉出来的,但数据量一旦十万百万等,会有明显的延迟。而如果我们给age建立了索引,那么这这个索引的数据将会是这样的。
index | age |
---|---|
18 | 18 |
19 | 19 |
20 | 20 |
18 | 18 |
此处index为索引标识符,里面存储的是标识符和数据地址,当我们用上面的SQL语句搜索时,步骤是,搜索索引标识符,通过索引标识符,得到所有age=18
的数据地址,然后依次取出来。
这样,便省去了检表的操作。
这样,是不是省去了不少时间。
Fulltext的效果就是这样的。
英文全文搜索的三个模式
- 自然搜索模式
MySQL会把搜索字符串解析成一系列的单词,然后搜索出保函这些单词的所在行。
SELECT * FROM index_article WHERE MATCH(content) AGAINST('中国 北京')
这条语句意思为:搜索索引表中字段为content中包含关键词'中国'、'北京'的文章。
- 布尔搜索模式
在搜索的字符串中可以存在修饰符
SELECT * FROM index_article WHERE MATCH(content) AGAINST('+北京 -朝阳' IN BOOLEAN MODE)
这条语句的意思为搜索索引表中包含'北京'不包含'朝阳'的文章。
- 扩展搜索模式
两次搜索,第一次搜索是自然搜索,第二次搜索会吧第一次搜索中内容中所有次再次进行匹配,从而达到扩大饭的效果。
SELECT * FROM index_article WHERE MATCH(content) AGAINST('中国 北京' WITH QUERY EXPANSION)
这句的效果是,当第一次搜索时候,搜索到了一句北京朝阳男性市民
,第二次扩展搜索时,会将男性
和市民
加入搜索行列,即所有出现这些关键词的时候,都会被搜索到。
权重,怎么计算?
一条语句搞定,但是中文特殊一点,在源码中我有使用到。
SELECT id, MATCH(content) AGAINST('中国 北京') AS weight FROM index_article
之前写的条件变成了权重显示的字段。这条语句的效果为,所有匹配到这两个关键词的内容,会根据次数,密度,比例等指标计算权重。示例如下:
id | weight |
---|---|
1 | 1.2323232123 |
2 | 1.3423423423 |
3 | 2.3421231231 |
4 | 0.2213123123 |
好,基本知识普及完成,接下来讲下中文全文搜索的思路以及源码
思路解析
项目:做一个博客,可以进行文章上传、展示、站内全文关键词搜索、搜索关键字提示、搜索权重分析。
项目思路:对文章和标题进行分词操作,分词写入数据库的索引表,原数据写入原表;关键词高亮显示,可以使用字符串替换;权重计算关键词出现的次数。
数据表截图
索引表1.png
索引表2.png
原表1.png
原表2.png
多插入几条数据才能实现
展示页面+搜索
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="http://www.zh30.com/wp-content/themes/weisayheibai/style.css">
<style>
h1 {font-size:32px;}
html #hm_t_undefined .hm-t-go-top {
position:fixed;right:2px;bottom:2px;z-index:99998;cursor:pointer;width:40px;height:37px!important;text-align:center;white-space:normal;font-size:14px;line-height:17px;padding-top:3px;color:#fff;background:#404040;
}
.entry_post{
height:100px;overflow: hidden;white-space:wrap;text-overflow:ellipsis;
}
</style>
<script src="../js/jquery.min.js"></script>
</head>
<body>
<div id="page">
<div id="header">
<div class="open-follow">
<table class="rss"><tbody><td>
<div class="search">
<form method="GET" id="searchform" action="#">
<fieldset id="search"><span>
<input type="search" name="search" style="height:23px;float:left;">
<input type="submit" value="GO" style="background:#555;font-size:18px;color:white;cursor:pointer;">
</span></fieldset>
</form>
</div>
</td></tr></tbody>
</table>
</div>
<div class="clear"></div>
<div id="blogname">
<a href="http://www.zh30.com/"><h1>个人博客</h1></a>
<div id="blogtitle">个人网站</div>
</div>
<div class="pagemenu">
<ul id="menu-pages-menu" class="navi">
<li class="page_item page-item-3"><a href="show.php" title="首页">首页</a></li>
<li class="page_item page-item-285"><a href="">地图</a></li>
<li class="page_item page-item-2"><a href="" rel="nofollow">关于</a></li>
<li class="page_item"><a href="upload.html">发布文章</a></li>
</ul>
</div>
</div>
<div id="content">
<div class="content_l">
<div class="post-2156 post type-post status-publish format-standard hentry category-linux-2 tag-centos tag-fedora tag-git" id="post-2156">
<div class="left">
<!-- 文章模块开始-->
<div class="article">
<?php
// // 包涵类文件
require("../model/model.php");
require("../model/segment.php");
$model = new DB();
$segment = new segment();
if(!empty($_GET['search'])){
$keywords = htmlentities($_GET['search']);
$key = $segment->get_keyword($keywords);
unset($keywords);// 释放变量
// 准备sql语句
$sql = "SELECT fid FROM index_article WHERE MATCH(title,content) AGAINST('".$key."')";
$rtn = $model->get_all($sql);
unset($sql);// 释放变量
$keys = explode(' ', $key);// 将关键字分割成数组
$res = [];// 建立空数组存储数据
for($i=0;$i<count($rtn);$i++){
$fid = $rtn[$i]['fid'];
$sql = "SELECT * FROM article WHERE id = '".$fid."'";
$res[$i] = $model->get_one($sql);
unset($sql);// 销毁变量
//这边做一个相似度评级
$res[$i]['title_num'] = 0;
$res[$i]['content_num'] = 0;
for($j=0;$j<count($keys);$j++){
$res[$i]['title_num'] += substr_count($res[$i]['title'],$keys[$j]);
$res[$i]['content_num'] += substr_count($res[$i]['content'],$keys[$j]);
}
// 计算权重
$res[$i]['weight'] = $res[$i]['title_num'] * 90 + $res[$i]['content_num'] * 10;
}
// 根据权重新排序数组
foreach($res as $k=>$v){
$weight[$k] = $v['weight'];
}
@array_multisort($weight,SORT_NUMERIC,SORT_DESC,$res);
// 销毁变量
unset($rtn);
unset($weight);
}else{
$sql = "SELECT * FROM article";
$res = $model->get_all($sql);
unset($sql);
}
$str = '';
for($i=0;$i<count($res);$i++){
$str .= '<div class="article_t">';
$str .= '<div class="article_b">';
$str .= '<h2><a href="details.php?id='.$res[$i]['id'].'" title="权重:'.$res[$i]['weight'].'"rel="bookmark">'.$res[$i]['title'].'</a></h2>';
$str .= '<p class="entry_post">'.$res[$i]['content'].'</p>';
$str .= '<p class="more_b"><span class="more"><a href="details.php?id='.$res[$i]['id'].'" rel="bookmark nofollow">阅读全文...</a></span></p>';
$str .= '<div class="clear"></div>';
$str .= '</div></div>';
}
// // $s = '';
// 替换多个关键字
for($i=0;$i<count($keys);$i++){
$str = str_replace($keys[$i],"<font color='red'>".$keys[$i]."</font>",$str);
}
echo $str;
// 释放变量
unset($keys);
unset($str);
?>
</div>
<!--分页开始-->
<div class="navigation">
<div class="pagination">
<span class="current">1</span>
<a href="http://www.zh30.com/page/2" class="inactive">2</a>
<a href="http://www.zh30.com/page/3" class="inactive">3</a>
<a href="http://www.zh30.com/page/4" class="inactive">4</a>
<a href="http://www.zh30.com/page/5" class="inactive">5</a>
<a href="http://www.zh30.com/page/6" class="inactive">6</a>
<a href="http://www.zh30.com/page/2" class="page_next">下一页</a>
<a href="http://www.zh30.com/page/56">最后</a>
</div>
</div>
</div>
</body>
</html>