{"id":104021,"date":"2024-10-25T03:56:00","date_gmt":"2024-10-25T03:56:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=104021"},"modified":"2025-06-30T11:33:20","modified_gmt":"2025-06-30T11:33:20","slug":"mysql-index-overviews-fulltext-b-tree-indexes","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/mysql-index-overviews-fulltext-b-tree-indexes\/","title":{"rendered":"MySQL Index Overviews: FULLTEXT B-Tree Indexes"},"content":{"rendered":"\n<p>If you\u2019ve been following <a href=\"https:\/\/www.red-gate.com\/simple-talk\/\">Simple Talk<\/a> for a while, you know that we\u2019re fans of databases and everything they involve. We\u2019re also big fans of database performance, too. And if you\u2019re a developer or a DBA, you surely know one of the primary answers to database performance \u2013 indexing! <a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/optimizing-mysql-the-basics-of-query-optimization\/\">We\u2019ve already told you about indexes when we talked about multiple approaches to improve your query performance<\/a> and we\u2019ve already told you about <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/the-nuances-of-mysql-indexes\/\">indexing nuances<\/a> during some of our previous blog posts, too.<\/p>\n\n\n\n<p>We\u2019ve also walked you through the default \u2013 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/mysql-index-overviews-composite-b-tree-indexes\/\">B-Tree \u2013 type of indexes in MySQL<\/a>. Now it\u2019s time to dive into its cousin \u2013 full-text indexing!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-fulltext-indexing\">What is Fulltext Indexing?<\/h2>\n\n\n\n<p>Full-text indexes are special \u2013 they\u2019re a type of index that enables fast and efficient searching through data using rather exotic search modes: in MySQL, those search modes include, but are not limited to the Boolean mode, natural language mode, and a search mode using query expansion. Some of the search modes made possible by using full-text indexes provide unique support for interpreting specific characters differently, searching for implied data, and other specific nuances.<\/p>\n\n\n\n<p>Full-text indexing works similarly across multiple database management systems and hasn\u2019t changed much during the years \u2013 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/understanding-full-text-indexing-in-sql-server\/\">take a look at Robert Sheldon&#8217;s blog on full-text indexes from more than two decades ago<\/a> and you will surely find something that\u2019s changed in your SQL Server installation, but you will also find a lot of things that remained the same; the same can be said about other database management systems.<\/p>\n\n\n\n<p>Full-text search capabilities are a means to an end: a means to add full-text search functionality onto their application or website or perform other complex searches through bigger data sets. They allow your search engine to act a little like Google does: it allows your database to return relevant results without you asking for them by using the expansion mode, allows you to search with fuzzy matching using the * character as the wildcard, allows you to search for exact matches by wrapping your query in double quotes, and has other unique capabilities.<\/p>\n\n\n\n<p>Grab the data used in this example from <a href=\"https:\/\/pst.innomi.net\/paste\/drn3hbsfzsvcqxz4bc5a3b4g\">this link<\/a>, and it is included in the Appendix as well.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-fulltext-indexes-in-mysql\">Fulltext Indexes in MySQL<\/h2>\n\n\n\n<p>In MySQL, full-text indexes facilitate searches of 3 types:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>Searching <em>using a natural language search mode<\/em> is made possible when no search mode is specified or when searches are conducted in the <code>NATURAL LANGUAGE<\/code> <code>MODE<\/code>.<\/li>\n\n\n\n<li>Full-text searches <em>with query expansion<\/em> are possible using assumed knowledge and can be defined if we define <code>WITH QUERY EXPANSION<\/code> at the end of our query. Queries using such a search mode will run twice \u2013 once running the search in a default mode (the natural language mode), and the second time by adding the most relevant results from the first search and running the search again, thus coming back with an interesting result set in the end.<\/li>\n\n\n\n<li><em>Boolean full-text searches<\/em> can be helpful if your application needs to search through data using various Boolean characters that have a varied meaning (\u201c~\u201d lowers the contribution of the value to the search results, \u201c-\u201c tells your database that the value mustn\u2019t be included, searching using double quotes only matches literal values, etc. Such searches necessitate the definition <code>IN BOOLEAN MODE<\/code>.)<\/li>\n<\/ol>\n<\/div>\n\n\n<p>It\u2019s also worth knowing that when full-text indexes are in use, certain characters are represented differently. For example, if our column doesn\u2019t have a full-text index on top of it and we\u2019re using a wildcard to power our searches, the wildcard needs to be represented as the \u201c<code>%<\/code>\u201d character while if full-text indexes are in use the wildcard becomes the \u201c<code>*<\/code>\u201d character and the \u201c<code>%<\/code>\u201d character won\u2019t work.<\/p>\n\n\n\n<p>These things considered, full-text indexes in MySQL can be defined on <code>CHAR<\/code>, <code>VARCHAR<\/code>, and <code>TEXT<\/code> columns, and they should be defined like so:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">CREATE TABLE `demo_table` (\n   `varchar_col` VARCHAR(60) DEFAULT \u2018\u2019, \n   `number_col` INT DEFAULT 0,\n   `other_col` TEXT,\n   -- And other columns\n   --FULLTEXT Added here\n   FULLTEXT varchar_idx(varchar_col)\n);<\/pre>\n\n\n\n<p>If you want to define a full-text index on a table once it\u2019s already created, make use of an ordinary <code>ALTER<\/code> clause with a <code>FULLTEXT<\/code> definition:<\/p>\n\n\n\n<p><code>ALTER TABLE `people` ADD FULLTEXT(first_name);<\/code><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"682\" height=\"82\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-26.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Defining a FT Index - ALTER.png\" class=\"wp-image-104063\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 1 &#8211; Defining a Full-text Index when Modifying a Table<\/em><\/p>\n\n\n\n<p>Fulltext indexes can also be created using the <code>CREATE FULLTEXT INDEX<\/code> clause:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"805\" height=\"183\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-27.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Defining a FT Index - ALTER and CREATE.png\" class=\"wp-image-104064\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 2 &#8211; Creating Fulltext Indexes with CREATE FULLTEXT INDEX<\/em><\/p>\n\n\n\n<p><code>ALTER TABLE people ADD FULLTEXT(first_name);<\/code> would add a full-text index <code>first_name<\/code> on the column <code>first_name<\/code>, while <code>CREATE FULLTEXT INDEX `ft_idx` ON `people`(first_name); <\/code>would add a full-text index <code>ft_idx<\/code> on the <code>first_name<\/code> column.<\/p>\n\n\n\n<p>That means that if an index name is not specified, MySQL would create an index titled exactly as the column is titled. If an index name is specified, MySQL will create an index with a name that has been provided.<\/p>\n\n\n\n<p>On a related note, also keep in mind that MySQL will warn you if you use duplicate names for your indexes but won\u2019t prevent you from creating a different type of index on the same column.<\/p>\n\n\n\n<p>In other words, <em>you can have an ordinary B-tree index and a full-text index on the same column because they would serve two distinct purposes<\/em>: a B-tree index on the <code>first_name<\/code> column would satisfy queries like<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * \nFROM `people` \nWHERE `first_name` = \u2018Demo\u2019;<\/pre>\n\n\n\n<p>while a full-text index would satisfy columns like<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * <br>FROM `people` <br>MATCH(first_name) AGAINST(\u201cDemo\u201d)<\/pre>\n\n\n\n<p>Let\u2019s create a couple of indexes \u2013 you will see that MySQL will error out if we have a duplicate key name (that happens because indexes can be acted on depending on their names), but won\u2019t if we run multiple types of indexes:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"809\" height=\"185\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-28.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Duplicate Indexes.png\" class=\"wp-image-104065\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 3 &#8211; Multiple Indexes on a Column<\/em><\/p>\n\n\n\n<p>We can confirm that one column has multiple indexes by running a simple <code>DESCRIBE<\/code> query on our table (in this case, note the <code>MUL<\/code> \u2013 <em>multiple key<\/em> \u2013 definition below the <code>Key<\/code> column. In our cases, that means that we\u2019ve defined multiple indexes):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"663\" height=\"230\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-29.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\DESCRIBE a Table.png\" class=\"wp-image-104066\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 4 &#8211; Multiple Indexes on a MySQL Table<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-search-operations-using-fulltext-indexes-in-mysql\">Search Operations Using Fulltext Indexes in MySQL<\/h2>\n\n\n\n<p>Cool, you now know how to build full-text indexes in MySQL. Time to search for data!<\/p>\n\n\n\n<p>As I\u2019ve already told you before, full-text indexes in MySQL have a couple of search modes, these being the natural language search mode (this is the default search mode if no search mode is specified), Boolean search mode, and a mode with query expansion. Here\u2019s the syntax for full-text searches:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * \nFROM table_name\nWHERE MATCH(column_name) AGAINST(\u201cSearch Query\u201d [MODE]);<\/pre>\n\n\n\n<p>In our case, we\u2019ve defined a full-text index on the <code>first_name<\/code> column, which means that we can only run full-text search queries on that column. With that said, our query looks like so:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * \nFROM people \nWHERE MATCH(first_name) AGAINST(\u201cSearch Query\u201d [MODE]);<\/pre>\n\n\n\n<p>Let\u2019s try it out:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"885\" height=\"195\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-30.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Ordinary Full-text Index Example.png\" class=\"wp-image-104067\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 5 &#8211; Running a Sample Query on a Fulltext Index<\/em><\/p>\n\n\n\n<p>Specifying a search mode is not necessary unless you want to employ a specific full-text search mode. In that case, you would make use of one of three modes by defining them after the query.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-boolean-mode\">Boolean Mode<\/h3>\n\n\n\n<p>The purpose of a search in the Boolean mode is to search with \u201cfuzzy matching\u201d \u2013 such a search mode searches for given words and can act together with operators like less than (<code>&lt;<\/code>), more than (<code>&gt;<\/code>), the plus (<code>+<\/code>) and minus (<code>-<\/code>) signs (they denote \u201cmore importance\u201d or \u201cless importance\u201d for a result set), double quotes to search for an <em>exact<\/em> match, and <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/fulltext-boolean.html\">have other characteristics as outlined in the documentation<\/a>.<\/p>\n\n\n\n<p>For a search in the Boolean mode, everything would look like so:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * FROM people \nWHERE MATCH(first_name) \n          AGAINST(\u201cSearch Query\u201d IN BOOLEAN MODE);<\/pre>\n\n\n\n<p>You can see this and a few other examples in the following output:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1063\" height=\"856\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-31.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Example in Boolean Mode.png\" class=\"wp-image-104068\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 6 &#8211; Searches in Boolean Mode<\/em><\/p>\n\n\n\n<p>Such a query will return results after searching for a query in the Boolean mode. Here, keep in mind that there are a couple of \u201cgotchas\u201d, for example, ordinary wildcard characters like \u201c<code>%<\/code>\u201d and \u201c<code>_<\/code>\u201d won\u2019t work with full-text indexes \u2013 your search won\u2019t error out, but you will get an empty result set:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1048\" height=\"333\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-32.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Boolean Mode Wildcard Examples.png\" class=\"wp-image-104069\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 7 &#8211; Fulltext Indexes and Wildcards<\/em><\/p>\n\n\n\n<p>Boolean search is also named Boolean because it accepts Boolean symbols as part of search values. Have a look:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1169\" height=\"405\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-33.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\IN BOOLEAN MODE Example.png\" class=\"wp-image-104070\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 8 &#8211; Making Use of the Boolean Mode<\/em><\/p>\n\n\n\n<p>Here we told our database to search for records containing \u201c<code>Apple<\/code>\u201d and \u201c<code>juice<\/code>.\u201d We had a record \u201c<code>Apples with juice<\/code>\u201d as well as \u201c<code>Apple<\/code>\u201d so it\u2019d picked those up \u2013 very interesting!<\/p>\n\n\n\n<p>The Boolean operators available in the Boolean search mode are as follows:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>\u201c<code>+<\/code>\u201d means \u201cAND\u201d<\/li>\n\n\n\n<li>\u201c<code>-<\/code>\u201c means \u201cNOT\u201d<\/li>\n\n\n\n<li>\u201c<code> <\/code>\u201c (an empty operator) implies OR (that means if you search for \u201cApple juice\u201d, your database understands the search as \u201cApple OR Juice\u201d which may result in funny search results)<\/li>\n\n\n\n<li>\u201c<code>~<\/code>\u201d lowers the contribution of the row to the result set returned by MySQL<\/li>\n\n\n\n<li>\u201c<code>*<\/code>\u201d denotes a wildcard and should be appended to the word to take effect. Words beginning with the term preceding the * operator will be returned.<\/li>\n\n\n\n<li>\u201c<code>( )<\/code>\u201d denotes subexpressions.<\/li>\n\n\n\n<li>\u201c\u201d means \u201cexact match\u201d \u2013 your database will search for a literal phrase as it was typed.<\/li>\n<\/ul>\n<\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-query-expansion-mode\">Query Expansion Mode<\/h3>\n\n\n\n<p>A fulltext query expansion mode runs the search twice <em>relying on implied knowledge<\/em> meaning that a search for the term \u201cdatabase\u201d may return \u201cMySQL\u201d, \u201cMariaDB\u201d, \u201cPercona Server\u201d, \u201crelational\u201d, \u201cNoSQL\u201d, \u201cMongoDB\u201d, and the like \u2013 in essence, a query is run once, and then the second time searching for the most relevant results related to the query from the first searching attempt, and only then results are returned.<\/p>\n\n\n\n<p>Let\u2019s now try a search using query expansion. I\u2019ll use cars as an example:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1141\" height=\"386\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-34.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Query Expansion Examples.png\" class=\"wp-image-104071\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 9 &#8211; a Full-text Search with Query Expansion<\/em><\/p>\n\n\n\n<p>Full-text search didn\u2019t interpret our query as us searching for \u201c2008\u201d \u2013 <em>it\u2019d interpreted our search query as if we were searching for things made in 2008<\/em> and returned 3 results. It returned 3 results because the database thought all 3 results would be relevant \u2013 that\u2019s the entire point of searches with query expansion. Interesting, yeah?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-choosing-the-search-mode\">Choosing the Search Mode<\/h3>\n\n\n\n<p>Finally, decide on the search mode you\u2019re going to be using because using full-text indexes without a good reason isn\u2019t the smartest idea: they take up space, and if you don\u2019t know what you\u2019re doing, you may crash your server too (look at the <em>Full-text Server Crashing<\/em> example below.) Use full-text indexes if all of the following apply:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>You need to index <code>CHAR<\/code>, <code>VARCHAR<\/code>, or <code>TEXT<\/code> columns.<\/li>\n\n\n\n<li>You need the index to find keywords in the text.<\/li>\n\n\n\n<li>You are after one or more of the search capabilities that full-text indexes can provide (see examples above.)<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Once you\u2019ve made a conscious decision to employ full-text indexes inside of your database, you should also decide what search mode you are going to use. The search modes are something to think about because they\u2019re the primary reason people choose full-text indexes in the first place and if chosen improperly, they can impact the behavior of your search engine or other appliance you may be using full-text indexes for:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Use the <code>NATURAL LANGUAGE<\/code> mode (or don\u2019t define a mode) if you don\u2019t use wildcards and have no need for \u201cimplied knowledge\u201d searching, but still want to make use of a full-text index.<\/li>\n\n\n\n<li>Use the <code>BOOLEAN<\/code> mode if you intend to use Boolean characters (see examples above) or the wildcard character (*)<\/li>\n\n\n\n<li>Use the <code>QUERY EXPANSION<\/code> mode if your use case necessitates searches with implied knowledge e.g. \u201cdatabases\u201d are connected with database types, etc. See the example in the Query Expansion Mode heading.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>For more examples, see the heading below denoting all of the available full-text search modes and the limitations imposed on them.<\/p>\n\n\n\n<p>One should also account for the storage engine that is being used, and I\u2019ll explain why in a moment.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-fulltext-search-modes-and-limitations\">Fulltext Search Modes and Limitations<\/h2>\n\n\n\n<p>As I\u2019ve already mentioned and given examples above, full-text indexes in MySQL have three search modes you can put to the test:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><strong>Search in a natural language mode<\/strong> will be conducted if no mode has been defined or you explicitly define the <code>IN NATURAL LANGUAGE MODE<\/code> modifier. Searches using this modifier will not be able to use the features provided by the Boolean or query expansion modes, but they will provide an index to speed up search queries by helping them quickly find keywords in the text. For an example using this search mode, see below.<\/li>\n\n\n\n<li><strong>Search in a Boolean mode<\/strong> will be conducted if you specify the <code>IN BOOLEAN MODE<\/code> modifier. Once the modifier is in use, certain characters will have a special meaning. No operator, or a space, implies that you want to search for one value OR another value, \u201c+\u201d means <code>AND<\/code>, \u201c-\u201c means <code>NOT<\/code>, and \u201c~\u201d lowers the value\u2019s contribution to the search results. Double quotes (\u201c\u201d) would search for an exact match, and you would need to use the Boolean mode if you want to use the wildcard operator (*) to search with fuzzy matching.<\/li>\n\n\n\n<li><strong>Search in a query expansion mode<\/strong> will be conducted if you specify the WITH QUERY EXPANSION modifier. Such a full-text search mode is useful when we rely on implied knowledge. In other words, we can use this search mode when we\u2019re searching for a \u201cdatabase\u201d but want our result set to include the flavors of databases (\u201cMySQL\u201d, \u201cPostgreSQL\u201d, \u201cSQL Server\u201d, etc.) instead.<\/li>\n<\/ul>\n<\/div>\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1152\" height=\"629\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-35.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\IN NATURAL LANGUAGE MODE Example.png\" class=\"wp-image-104072\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Image 1 \u2013 A Search in a Natural Language Mode (Second Example)<\/p>\n\n\n\n<p>One thing to remember is that if you want to conduct a search using a full-text index, you must use the <code>MATCH() AGAINST()<\/code>syntax: if you use an ordinary \u201c<code>=<\/code>\u201d sign, your database won\u2019t use the full-text index you may\u2019ve defined (but may make good use of other types of indexes you may define instead.)<\/p>\n\n\n\n<p>As previously mentioned, full-text indexes are frequently invited to our database once we\u2019re after fuzzy matches for our data \u2013 in other words, if we\u2019re searching for something exotic. A wildcard using a full-text search mode becomes the star sign (*), and searches using it must use the Boolean mode:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * \nFROM table_name \nWHERE MATCH(column_name) AGAINST(\u201capple*\u201d IN BOOLEAN MODE);<\/pre>\n\n\n\n<p>A query like this is likely to find multiple results ranging from \u201c<code>apple<\/code>\u201d, \u201c<code>Jimmy has two apples<\/code>\u201d, \u201c<code>my grandma likes applesauce<\/code>\u201d, \u201c<code>I like this applet<\/code>\u201d, or \u201c<code>I\u2019ve downloaded this applet and it sucks!<\/code>\u201d, so you must be aware of such occurrences to not be disappointed (it\u2019s also worth noting that \u201c\u201d would denote an <em>exact match<\/em> while \u2018\u2019 would denote a <em>match<\/em>):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1115\" height=\"307\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-36.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Fulltext Things in Boolean Mode.png\" class=\"wp-image-104073\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 10 &#8211; Fulltext Search in Boolean Mode<\/em><\/p>\n\n\n\n<p>Don\u2019t forget the Boolean mode \u2013 without it, your query will still execute, albeit with a different result set <em>because the wildcard won\u2019t be acted on<\/em> as it\u2019s only available within the Boolean search mode:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"930\" height=\"184\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-37.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Wildcard Example without Boolean Mode.png\" class=\"wp-image-104074\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 11 &#8211; Fulltext Search without Boolean Mode<\/em><\/p>\n\n\n\n<p>Queries using the full-text search mode also have to be at least of 4 characters in length and:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Not consist of stopwords (<em>stopwords<\/em> are words that have a meaning for the database or for your use case if you define them yourself.) That means that your search may not be searching for any full-text stopwords from the InnoDB stopword list if you\u2019re running InnoDB, or from the MyISAM stopword list if you\u2019re running MyISAM (find a list of stopwords for InnoDB <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/fulltext-stopwords.html#:~:text=To%20define%20your%20own%20stopword,creating%20the%20full%2Dtext%20index.\">here<\/a>, and a list of stopwords for MyISAM <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.4\/en\/fulltext-stopwords.html#fulltext-stopwords-stopwords-for-myisam-search-indexes\">here<\/a>.) This point has caveats \u2013 see below*<\/li>\n\n\n\n<li>Not run on partitioned tables.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>*<em>MyISAM stopword list can be defined by setting the <\/em><code>ft_stopword_file<\/code><em> variable to the path name of the file that contains stopwords you need to be in place. To define a stopword list for the default storage engine \u2013 InnoDB \u2013 define a table with the structure of the <\/em><code>INNODB_FT_DEFAULT_STOPWORD<\/code><em> table (make sure the structure is exactly the same), then set the <\/em><code>innodb_ft_server_stopword_table<\/code><em> variable to the name of your created table.<\/em><\/p>\n\n\n\n<p>Also, keep in mind that the parser for queries using the <code>FULLTEXT<\/code> index in MySQL determines the start and end of words by looking at characters like the space (\u201c \u201c), comma (\u201c,\u201d), and the dot (\u201c.\u201d) That means that queries searching for anything including a space, a comma, or a dot may return unexpected results. See example below:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"993\" height=\"186\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/09\/d-misc-redgate-blogs-june-2024-mysql-index-deep-d-38.png\" alt=\"D:\\Misc\\Redgate\\Blogs\\June 2024\\MySQL Index Deep Dive - FULLTEXT Indexes\\Images\\Apple Juice Matches.png\" class=\"wp-image-104075\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Image 12 &#8211; I thought we&#8217;re searching for Apple Juice?<\/em><\/p>\n\n\n\n<p>Full-text search operations should adhere to the limitations discussed above, as well as not be searching through Chinese or Japanese data (such data will make the fulltext index unable to determine where words begin\/end), as well as provide AGAINST() with values instead of columns.<\/p>\n\n\n\n<p>See all restrictions imposed towards full-text search operations <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/fulltext-restrictions.html\">here<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-bugs-in-full-text-indexes-bug-104263\">Bugs in Full-text Indexes \u2013 BUG#104263<\/h2>\n\n\n\n<p>Older versions of MySQL (we\u2019re talking &lt;= MySQL 5.7 which is now deprecated and shouldn\u2019t be used anymore) also had a full-text-related trick up their sleeve \u2013 they\u2019ve been prone to server crashes if all three of the following were true:<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\">\n<li>There were more than 100 million rows in a table and one or more of our columns had a full-text index on it.<\/li>\n\n\n\n<li>There was an \u201c@\u201d sign in a specific column (think email addresses and everything in that realm.)<\/li>\n\n\n\n<li>You were searching for data containing the \u201c@\u201d sign using a full-text index with a Boolean search mode.<\/li>\n<\/ol>\n<\/div>\n\n\n<p>If all of these three factors were true and we would execute an SQL query like so (here we search for an exact match of <a href=\"mailto:demo@demonstration.com\">demo@demonstration.com<\/a> \u2013 the query could be anything with an \u201c@\u201d in it):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT * <br>FROM `any_table` WHERE MATCH(any_column) <br>           AGAINST(\u2018\u201ddemo@demonstration.com\u201d\u2019 IN BOOLEAN MODE);<\/pre>\n\n\n\n<p>Our SQL query would time out and we would have to terminate it before proceeding to run any other SQL queries in the database. Why? I assume because searching for an exact match (look closely \u2013 we\u2019ve wrapped our value in quotes meaning that <em>we\u2019re searching for exactly this value and nothing else<\/em>) and using an @ sign on bigger data sets causes MySQL to go bonkers. I\u2019ve never got to the bottom of this \u2013 but hey, now you have something to show during your next conference talk! (<a href=\"https:\/\/www.red-gate.com\/simple-talk\/blogs\/powershell-and-sql-on-the-river\/\">Also, if you\u2019re looking for a conference, visit our conference PowerShell and SQL on the River in August<\/a>.)<\/p>\n\n\n\n<p>This is a bug within MySQL \u2013 BUG#104263 to be exact. The bug exists only within MySQL 5.7 (if you find that it works on other versions, please let me know so I can test this out once again) and only works on bigger data sets and exact matches searching for anything containing the @ sign in the Boolean search mode.<\/p>\n\n\n\n<p>Cool \u2013 now you\u2019re also aware of a bug within MySQL!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-summary\">Summary<\/h2>\n\n\n\n<p>Fulltext indexes in MySQL are a force to be reckoned with \u2013 they\u2019re an extremely powerful friend you can call on whenever the need to implement various search modes (natural language search mode, query expansion search mode, or the Boolean search mode) occurs. If you want to implement a tad of Google-like search features into your search engine, look into full-text indexes.<\/p>\n\n\n\n<p>However, they must be accounted for and used properly \u2013 your database won\u2019t \u201cprotect\u201d you from adding a full-text index on top of another index on the same column, and full-text indexes are only necessary to complete search operations of a specific nature: they facilitate complex text searches.<\/p>\n\n\n\n<p>We hope that this blog was informational and useful for you, your colleagues, or the team you manage. If you elect to use full-text indexes in MySQL, do know both their benefits and disadvantages and choose wisely: if you want to deeper your knowledge of the database space, <a href=\"https:\/\/databasedive.com\">consider watching a video or two on the Database Dive YouTube channel<\/a> and also, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/\">come back to the Redgate blog every now and then to hear about updates from the database space<\/a>, and until next time.<\/p>\n\n\n\n<p>Appendix \u2013 Table Structure &amp; Data<\/p>\n\n\n\n<p><a id=\"post-104021-_heading=h.gjdgxs\"><\/a> In case you want to create the tables and indexes in this blog, all tables, table structures, and demo data are available <a href=\"https:\/\/pst.innomi.net\/paste\/drn3hbsfzsvcqxz4bc5a3b4g\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019ve been following Simple Talk for a while, you know that we\u2019re fans of databases and everything they involve. We\u2019re also big fans of database performance, too. And if you\u2019re a developer or a DBA, you surely know one of the primary answers to database performance \u2013 indexing! We\u2019ve already told you about indexes&#8230;&hellip;<\/p>\n","protected":false},"author":339547,"featured_media":104080,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,145792],"tags":[5854],"coauthors":[146040],"class_list":["post-104021","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-featured","category-mysql","tag-mysql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/104021","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/339547"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=104021"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/104021\/revisions"}],"predecessor-version":[{"id":107327,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/104021\/revisions\/107327"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/104080"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=104021"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=104021"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=104021"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=104021"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}