可能在搜索“女人”,而结果搜索出的前几条记录都是把“女人”和“皮包”放在一起,这将导致最后得到的结果是关于“皮包”的搜索。因为MySQL会认为“皮包”属于“女人”这个类别。
6.2.5微调MySQL全文搜索
为了更加有效,需要对全文搜索谨慎调节。实际上,在大多数情况下,修改默认性能只能降低其性能。除非清楚自己在做什么,否则不要改变MySQL源。
大多数全文变量必须在服务器启动时被设置。为了改变它们,还要重新启动服务器;一些变量的改变需要重建表中的FULLTEXT索引。
1.被编入索引单词的长度
MySQL全文搜索将任何单字字符(字母、数字和下划线部分)的序列视为一个单词。这个序列或许也包含单引号("),但在一行中不会超过一个。这意味着aaa"bbb会被视为一个单词,而aaa""bbb则被视为2个单词。位于单词之前或其后的单引号会被全文搜索分析程序去掉;"aaa"bbb"会变成aaa"bbb。
全文搜索分析程序会通过寻找某些分隔符来确定单词的起始位置和结束位置,例如,间隔符号("")、逗号(,)及句号(.)。假如单词没有被分隔符分开,(例如在中文里),则全文搜索分析程序不能确定一个词的起始位置和结束位置。为了能够在这样的语言中向全文索引添加单词或其他编入索引的术语,必须对它们进行预处理,使其被一些诸如间隔符之类的任意分隔符分隔开。
ft_min_word_len和ft max word len这两个系统自变量规定了被编入索引单词的最小长度和最大长度,默认的最小值为四个字符;默认的最大值取决于使用的MySQL版本。
假如改变任意一个值,那么就必须重建FULLTEXT索引。例如,若你希望一个3字符的单词变为可查找项,则可以通过将以下行移动到一个供选择文件里,从而设置ft_min_word_len变量:
[mysqld]
ft_min_word_len=3
然后重新启动服务器,重建FULLTEXT索引。
2.覆盖默认忽略词
若要覆盖默认忽略词,则可设置ft stopword file系统变量。变量值应为包含忽略词的文件路径名,或是用来截止禁用词过滤的空字符串。在改变了这个变量的值或禁用词文件的内容后,重建你的FULLTEXT索引。
忽略词是自由形态的,换言之,可使用任何诸如newline,space或comma这样的非字母数字字符来分隔禁用词。下划线字符()和被视为单词的一部分的单引号(")例外。
3.50%阈值
自然语言查询的50%阈值由所选择的特别权衡方案所决定。若要阻止它,首先在myisam/ftdefs.h中寻找以下行:
# define GWS IN USE GWS PROB
将该行改为:
# define GWS IN USE GWS FREQ
然后重新编译MySQL。此时不需要重建索引。
注意:这样做会严重降低MySQL为MATCH()函数提供合适的相关值的能力。假如真的需要搜索这样的普通词,建议使用INBOOLEAN MODE,因为它不遵循50%阈值。
4.改变用于布尔全文搜索的操作符
要改变用于布尔全文搜索的操作符,要设置ft boolean syntax系统变量。这个变量也可以在服务器运行时被改变,但必须有SUPER特权才能这么做。在这种情况下不需要重建索引。
假如改变了影响索引的全文变量(ft_min_word_len、ft max word len或ft stopwordfile)。或假如改变了禁用词文件本身,则必须在改变和重新启动服务器后重建FULLTEXT索引。这时,要重建索引,只需进行一个QUICK修理操作,如下所示。
mysql>REPAIR TABLE tbl name QUICK;
注意,假如使用myisamchk来执行一项修改表索引的操作(诸如修理或分析)。则使用最小单词长度和最大单词长度,以及忽略词的默认全文参数值重建FULLTEXT索引,除非已另外指定。这将导致问询失败。发生这个问题的原因在于只有服务器认识这些参数。它们的存储位置不在MyISAM索引文件中。若已经修改了最小单词长度或最大单词长度或服务器中的忽略词,为避免这个问题,应对mysqld所使用的myisamchk指定同样的ft min wordlen,ft max word len和ft stopword file值。例如,假如已经将最小单词长度设置为3,则可以修改一个带有myisamchk的表,如下所示。
shell>myisamchk——recover——ft_min_word_len=3tbl name.MYI为保证myisamchk及服务器对全文参数使用相同的值,可将每一项都放在供选文件中的[mysqld]和[myisamchk]部分,如下所示。
[mysqld]
ft_min_word_len=3
[myisamchk]
ft_min_word_len=3
使用REPAIR TABLE,ANALYZE TABLE,OPTIMIZE TABLE或ALTER TABLE来代替使用myisamchk。这些语句通过服务器来执行,服务器知道使用哪个全文参数值更加合适。
6.3应用MySQL全文搜索
6.3.1桌面应用
这里,使用Java Swing开发一个桌面应用程序,连接MySQL数据库,执行全文搜索。这个案例只为演示。
只含有一个文件TianenSearch.java,代码如下:
TianenSearch.java
import java.util.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.sql.*;
public class TianenSearcher
{
//搜索关键词
static JTextField jtf=null;
//搜索按钮
static JButton jb=null;
//文本区域
static JTextArea jta;
private static void createAndShowGUI()
{
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame=new JFrame("MySQL Searcher!yutianen@163.com");
frame.setDefaultCloseOperation(JFrame.EXIT ONCLOSE);
Container con=frame.getContentPane();
con.setLayout(newBorderLayout());
JPanel jpup=new JPanel();
jpup.setLayout(new GridLayout(1,2));
jtf=new JTextField(30);
jb=new JButton("搜索");
jb.addActionListener
(
newActionListener()
{
public void actionPerformed(ActionEvent e)
{
String s=jtf.getText().trim();
jta.setText(search(s));
}
}
);
jpup.add(jtf);
jpup.add(jb);
jta=new JTextArea(10,60);
JScrollPane jsp=new JScrollPane(jta);
con.add(jpup,BorderLayout.NORTH);
con.add(jsp,BorderLayout.CENTER);
frame.setSize(200,100);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater
(
new Runnable()
{
public void run()
{
createAndShowGUI();
}
}
);
}
private static String search(String s)
{
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
StringBuffer res=new StringBuffer("");
try
{
Class.forName("com.mysql.jdbc.Driver");
}
catch(ClassNotFoundException ce)
{
System.out.println(ce.getMessage());
}
try
{
//获得Connection对象
String url="jdbc:mysql://localhost:3306/tianen";
conn=DriverManager.getConnection(url,"root","tianen");
stmt=conn.createStatement();
String sql="SELECT*FROM en where match(title,body)against(""+s+"")";
rs=stmt.executeQuery(sql);
while(rs.next())
{
res.append(rs.getString("title")+" "+rs.getString("body")+"
");
}
}
catch(SQLException e)
{
System.out.println(e.getMessage());
}
finally
{
if(rs!=null)
{
try
{
rs.close();
}
catch(SQLException ee)
{
System.out.println(ee.getMessage());
}
}
if(stmt!=null)
{
try
{
stmt.close();
}
catch(SQLException ee)
{
System.out.println(ee.getMessage());
}
}
if(conn!=null)
{
try
{
conn.close();
}
catch(SQLException ee)
{
System.out.println(ee.getMessage());
}
}
}
return res.toString();
}
}