I’m using MySQL 8.0.35
and I’ve two tables in my database: users
and clients
. Users can be searched by username
, firstname
, lastname
, email
, phone
, document
or even id
. The clients
table is used to track which users are clients of a specific business, with different statuses indicating their client state:
- If a user does not appear, it means they have never been a client.
status = 1
indicates that they are pending.status = 2
means they are currently a client.status = 0
means they were a client at some point but are no
longer a client now.
I don’t know the exact input the user will provide. There’s no option for them to specify if they are searching by email
, document
or other fields. I perform some checks before querying to determine the data type but it’s mostly based on the input characteristics (like checking for an @
for emails or numeric only for phone numbers and documents). Here are the table structures and the current queries I use for various search inputs:
`Db`.`users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`email` VARCHAR(255) NULL,
`username` VARCHAR(50) NULL,
`firstname` VARCHAR(30) NULL,
`lastname` VARCHAR(60) NULL,
`document` CHAR(11) NULL,
`phone` VARCHAR(15) NULL,
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
UNIQUE INDEX `email_UNIQUE` (`email` ASC) VISIBLE,
UNIQUE INDEX `username_UNIQUE` (`username` ASC) VISIBLE,
UNIQUE INDEX `document_UNIQUE` (`document` ASC) VISIBLE)
ENGINE = InnoDB;
`Db`.`clients` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`status` TINYINT(1) UNSIGNED NOT NULL,
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NULL,
`userId` INT UNSIGNED NOT NULL,
`businessId` INT UNSIGNED NOT NULL,
PRIMARY KEY (`id`, `userId`, `businessId`),
UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
INDEX `fk_clients_users1_idx` (`userId` ASC) VISIBLE,
INDEX `fk_clients_business1_idx` (`businessId` ASC) VISIBLE,
CONSTRAINT `fk_clients_users1`
FOREIGN KEY (`userId`)
REFERENCES `Db`.`users` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_clients_business1`
FOREIGN KEY (`businessId`)
REFERENCES `Db`.`business` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
Query without input:
SELECT u.id, u.firstname, u.lastname, u.phone
FROM clients c
INNER JOIN users u ON c.userId = u.id
WHERE c.businessId = 1 AND c.status = 2
ORDER BY c.id DESC
Query with numeric input:
SELECT u.id, u.firstname, u.lastname, u.phone
FROM clients c
INNER JOIN users u ON c.userId = u.id
WHERE c.businessId = 1 AND c.status = 2 AND (u.id = $search OR u.document = $search OR u.phone = $search)
ORDER BY c.id DESC
Query with email input (input contains @
):
SELECT u.id, u.firstname, u.lastname, u.phone
FROM clients c
INNER JOIN users u ON c.userId = u.id
WHERE c.businessId = 1 AND c.status = 2 AND u.email = '$search'
ORDER BY c.id DESC
Query with alphanumeric input (username
, firstname
, lastname
):
SELECT u.id, u.firstname, u.lastname, u.phone
FROM clients c
INNER JOIN users u ON c.userId = u.id
WHERE c.businessId = 1 AND c.status = 2 AND (u.username LIKE '$search%' OR CONCAT(u.firstname, ' ', u.lastname) LIKE '$search%' OR u.lastname LIKE '$search%')
ORDER BY c.id DESC
-
Which indexes should I create to optimize these queries? Do I need
an index onstatus
as well considering it’s used in almost every
query? (I filter several other queries bystatus
as well) -
Given that I’m currently using
LIKE
for partial matches, is it
better to switch to full-text search for performance reasons? Should
I consider creating afullName
column and indexing it for such
searches? Previously I was usingLIKE
with a wildcard%
both before and after the input for partial matching. To improve performance, I changed it to use the wildcard only at the end, which already helped to some extent. -
I’ve read that using the
OR
operator can be less efficient. Should
I consider using aUNION
instead, where each part of the search
criteria is handled in a separate query combined withUNION
?
Any advice on how to best optimize these queries and indexing strategies would be greatly appreciated. I understand there are several alternatives such as Elasticsearch, Algolia, etc. but I would like to extract as much as possible through MySQL. Thanks!
2
Answers
All your queries will be helped by an index on
clients(businessId, status)
. You can reverse the order of those two columns if you prefer.The other indexes you have look like they might be useful. Suggest you use
EXPLAIN ANALYZE
(justANALYZE
on MariaDB) to look at the execution plans for these queries and see whether any of them do full scans. If they do, you may need another index.Read this to learn about this topic https://use-the-index-luke.com/
INDEX(businessid, status)
is likely to be great for the first (no input) query.What percentage of rows have
c.businessId = 1 AND c.status = 2
? If it is a large percentage, may be shunned as being not worth using.Perhaps it is more useful to have multiple indexes on the other table
For
FULLTEXT, you need to use
MATCH(…) AGAINST (…)`Also, note that the columns of
MATCH
must agree with theFULLTEXT
index`. That implies, for example, that you needif you wat to test
FULLTEXT
is usually very fast, and is likely to be used in preference to a regularINDEX
. But be aware of its limitations. I suspect than "James, Richard" and "Richards, James" would be synonymous.(Having 5
UNIQUE
indexes (including the PK) on a table is unusual, but not impossible.)