Search code examples
mysqlquery-optimizationgoogle-cloud-sqldatabase-performance

2 where clauses slower than separated queries


I'm using Google Cloud SQL (the micro server version) to run a couple of performance tests.

I want to do the following query:

select count(*) from table where A = valueA and B like "%input_string%";
+----------+
| count(*) |
+----------+
|   512997 |
+----------+
1 row in set (9.64 sec)

If I run them separately, I get:

select count(*) from table where A = valueA;
+----------+
| count(*) |
+----------+
|   512998 |
+----------+
1 row in set (0.18 sec)

select count(*) from table where B like "%input_string%";
+----------+
| count(*) |
+----------+
|   512997 |
+----------+
1 row in set (1.43 sec)

How is that difference in performance possible???

Both A and B columns have indexes as they are used to order tables in a web application.

Thx! EDIT: table schema

table | CREATE TABLE `table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `A` varchar(9) DEFAULT NULL,
  `B` varchar(50) DEFAULT NULL,
  `C` varchar(10) DEFAULT NULL,
  `D` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `A` (`A`),
  KEY `B` (`B`)
) ENGINE=InnoDB AUTO_INCREMENT=512999 DEFAULT CHARSET=utf8

Solution

  • A option might be using a FULLTEXT INDEX and using MATCH() on it.

    CREATE TABLE `table` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `A` varchar(9) DEFAULT NULL,
      `B` varchar(50) DEFAULT NULL,
      `C` varchar(10) DEFAULT NULL,
      `D` varchar(50) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY(A), 
      FULLTEXT INDEX(B)
    ) ENGINE=InnoDB AUTO_INCREMENT=512999 DEFAULT CHARSET=utf8
    

    And a query rewrite

    SELECT 
     count(*)
    FROM 
    `table`
    WHERE
       A = 'A'
     AND 
       B IN (
    
         SELECT 
          B
         FROM 
          `table`
         WHERE
       MATCH(B) AGAINST('+input_string' IN BOOLEAN MODE)
    )
    

    The inner SQL will filter down a possible result based on the FULLTEXT index.
    And the outer SQL will do the other filtering.

    You could also use a UNION ALL now i think about it.
    It should work on this questions CREATE TABLE statement .
    The general idea is to get two counts for both filters and pick the lowest as valid count.

    Query

    SELECT 
     MIN(counted) AS 'COUNT(*)' # Result 512997
    FROM (
    
      select count(*) AS counted from `table` where A = 'A' # Result 512998
      UNION ALL
      select count(*)  from `table` where B like "%input_string%" # Result  512997
    ) AS counts