skip to Main Content

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
  1. Which indexes should I create to optimize these queries? Do I need
    an index on status as well considering it’s used in almost every
    query? (I filter several other queries by status as well)

  2. 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 a fullName column and indexing it for such
    searches? Previously I was using LIKE 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.

  3. I’ve read that using the OR operator can be less efficient. Should
    I consider using a UNION instead, where each part of the search
    criteria is handled in a separate query combined with UNION?

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


  1. 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.

    CREATE INDEX business_id_status ON client(businessId, status);
    

    The other indexes you have look like they might be useful. Suggest you use EXPLAIN ANALYZE (just ANALYZE 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/

    Login or Signup to reply.
  2. 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

    INDEX(email),
    FULLTEXT(username, firstname, lastname)
    etc
    

    For FULLTEXT, you need to use MATCH(…) AGAINST (…)`

    Also, note that the columns of MATCH must agree with the FULLTEXT index`. That implies, for example, that you need

    FULLTEXT(firstname, lastname)
    

    if you wat to test

    MATCH(firstname, lastname) AGAINST(...)
    

    FULLTEXT is usually very fast, and is likely to be used in preference to a regular INDEX. 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.)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search