Can someone explain to me why I'm seeing the following behavior:
mysql> show index from history_historyentry;
+----------------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| history_historyentry | 0 | PRIMARY | 1 | id | A | 48609 | NULL | NULL | | BTREE | |
+----------------------+------------+------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
1 row in set (0.00 sec)
mysql> explain SELECT COUNT(*) FROM `history_historyentry` WHERE `history_historyentry`.`is_deleted` = False;
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | history_historyentry | ALL | NULL | NULL | NULL | NULL | 48612 | Using where |
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
mysql> explain SELECT COUNT(*) FROM `history_historyentry` WHERE `history_historyentry`.`is_deleted` = True;
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
| 1 | SIMPLE | history_historyentry | ALL | NULL | NULL | NULL | NULL | 48613 | Using where |
+----+-------------+----------------------+------+---------------+------+---------+------+-------+-------------+
1 row in set (0.00 sec)
mysql> create index deleted on history_historyentry (is_deleted) ;
Query OK, 48627 rows affected (0.38 sec)
Records: 48627 Duplicates: 0 Warnings: 0
mysql> explain SELECT COUNT(*) FROM `history_historyentry` WHERE `history_historyentry`.`is_deleted` = False;
+----+-------------+----------------------+-------+---------------+---------+---------+------+-------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------------+-------+---------------+---------+---------+------+-------+--------------------------+
| 1 | SIMPLE | history_historyentry | index | deleted | deleted | 1 | NULL | 36471 | Using where; Using index |
+----+-------------+----------------------+-------+---------------+---------+---------+------+-------+--------------------------+
1 row in set (0.00 sec)
mysql> explain SELECT COUNT(*) FROM `history_historyentry` WHERE `history_historyentry`.`is_deleted` = True;
+----+-------------+----------------------+------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------------------+------+---------------+---------+---------+-------+------+-------------+
| 1 | SIMPLE | history_historyentry | ref | deleted | deleted | 1 | const | 166 | Using index |
+----+-------------+----------------------+------+---------------+---------+---------+-------+------+-------------+
1 row in set (0.00 sec)
Why the descrepancy in index usage for True vs False? Specifically, in the false case, the ref column is NULL, and the extra column is Using where; Using index. But in the true case, the ref column is const, and the extra column is Using index.
Presumably because one gives good selectivity and the other does not, i.e. only a small percentage of the rows are deleted.
A cost based optimiser will only use an index if it provides good selectivity (typically 10%) or possibly if it is a covering index (one which satifies a query without a further table or bookmark lookup).
Related
Assuming I have a table as:
create table any_table (any_column_1 int, any_column_2 varchar(255));
create index any_table_any_column_1_IDX USING BTREE ON any_table (any_column_1);
(Note: Index type should not matter here)
I was wondering if querying any_column with int or string have any impact on performance, i.e. does
select * from any_table where any_column_1 = 12345;
have any differences in terms of performance with this one?
select * from any_table where any_column_1 = '12345';
I have looked around the web and really have not faced this particular case.
It should be fine to do this either way for an indexed integer column. When you compare an integer column to a constant, the constant value is cast to an integer whether you format it as an integer or a string.
You can confirm this with EXPLAIN. In both cases, the EXPLAIN shows that it will use the index (type: ref indicates an index lookup), and the performance will be the same.
mysql> explain select * from any_table where any_column_1 = 12345;
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | any_table | NULL | ref | any_table_any_column_1_IDX | any_table_any_column_1_IDX | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
mysql> explain select * from any_table where any_column_1 = '12345';
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | any_table | NULL | ref | any_table_any_column_1_IDX | any_table_any_column_1_IDX | 5 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
If you had indexed the string column in your example, any_column_2, it would make a difference because the collation of a string column must match the collation of the value you compare it to. A string literal will be cast to a compatible collation by default, so it uses the index:
create index any_table_any_column_2_IDX USING BTREE ON any_table (any_column_2);
mysql> explain select * from any_table where any_column_2 = '12345';
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | any_table | NULL | ref | any_table_any_column_2_IDX | any_table_any_column_2_IDX | 768 | const | 1 | 100.00 | NULL |
+----+-------------+-----------+------------+------+----------------------------+----------------------------+---------+-------+------+----------+-------+
But an integer literal has no collation, so you get warnings, and the index cannot be used. The EXPLAIN shows type: ALL so it will do a table-scan and that will have poor performance if you query a table with many rows.
mysql> explain select * from any_table where any_column_2 = 12345;
+----+-------------+-----------+------------+------+----------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | any_table | NULL | ALL | any_table_any_column_2_IDX | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+-----------+------------+------+----------------------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)
mysql> show warnings;
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Warning | 1739 | Cannot use ref access on index 'any_table_any_column_2_IDX' due to type or collation conversion on field 'any_column_2' |
| Warning | 1739 | Cannot use range access on index 'any_table_any_column_2_IDX' due to type or collation conversion on field 'any_column_2' |
| Note | 1003 | /* select#1 */ select `test2`.`any_table`.`any_column_1` AS `any_column_1`,`test2`.`any_table`.`any_column_2` AS `any_column_2` from `test2`.`any_table` where (`test2`.`any_table`.`any_column_2` = 12345) |
+---------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
I have a query for which i want to apply index .Query is
select * from table where (id1='12' AND id2='17') OR (id1='17' AND id2='12');
Now I tried to apply index with (id1,id2) but explain query showed me "ALL" for "type"
when i tried (id2,id1) or id1 and id2 still response was same. so how to index on id1 and id2 to handle above query.
mysql> explain select * from bothTable where (id1=12 and id2=17) OR (id1=17 and id2=12);
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | bothTable | ALL | NULL | NULL | NULL | NULL | 67 | Using where |
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> create index index_id1 ON bothTable (id1,id2);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from bothTable where (id1=12 and id2=17) OR (id1=17 and id2=12);
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | bothTable | ALL | index_id1 | NULL | NULL | NULL | 67 | Using where |
+----+-------------+------------------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
with union it is giving me below output :
+----+--------------+------------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------+------------------+------+---------------+------+---------+------+------+-------------+
| 1 | PRIMARY | bothTable | ALL | index_id1 | NULL | NULL | NULL | 67 | Using where |
| 2 | UNION | bothTable | ALL | index_id1 | NULL | NULL | NULL | 67 | Using where |
| NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | |
+----+--------------+------------------+------+---------------+------+---------+------+------+-------------+
3 rows in set (0.00 sec)
create index id1_id2 on table(id1, id2);
also, do not put '' around your integers if your id1 and id2 columns are integer columns, instead try this:
select * from table where (id1=12 AND id2=17) OR (id1=17 AND id2=12);
example:
mysql> explain select * from ff where (id1 = '12' and id2 = '17') or (id1 = '17' and id2 = '12');
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | ff | ALL | NULL | NULL | NULL | NULL | 7 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> create index id1_id2 on ff(id1,id2);
Query OK, 0 rows affected (0.15 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from ff where (id1 = 12 and id2 = 17) or (id1 = 17 and id2 = 12);
+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
| 1 | SIMPLE | ff | index | id1_id2 | id1_id2 | 10 | NULL | 7 | Using where; Using index |
+----+-------------+-------+-------+---------------+---------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
edit
since that still doesnt seem to be working, try this one out:
explain
select * from ff where (id1 = 12 and id2 = 17)
union
select * from ff where (id1 = 17 and id2 = 12);
I have table users with following columns.
id, name, updated_at
Query with explain plan
mysql> explain select * from users group by users.id order by users.updated_at desc limit 10;
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 190551 | Using filesort |
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
1 row in set (0.00 sec)
Created a new index
create index test_id_updated_at on users (id, updated_at);
After creating a new index still getting the same result with explain plan.
mysql> explain select * from users group by users.id order by users.updated_at desc limit 10;
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 190551 | Using filesort |
+----+-------------+-------------+------+---------------+------+---------+------+--------+----------------+
1 row in set (0.00 sec)
After forcing a new index in query, still getting same result.
I don't understand why it says 'Using filesort' after creating a new index.
I replicated your cenario, and mysql used the index:
mysql> explain SELECT * FROM test_index GROUP BY id ORDER BY updated_at DESC;
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | test_index | ALL | NULL | NULL | NULL | NULL | 393520 | Using temporary; Using filesort |
+----+-------------+------------+------+---------------+------+---------+------+--------+---------------------------------+
1 row in set (0.00 sec)
mysql> CREATE INDEX index1 ON test_index(id, updated_at);
Query OK, 0 rows affected (1.85 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain SELECT * FROM test_index GROUP BY id ORDER BY updated_at DESC;
+----+-------------+------------+-------+---------------+--------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+---------------+--------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | test_index | index | NULL | index1 | 12 | NULL | 393520 | Using temporary; Using filesort |
+----+-------------+------------+-------+---------------+--------+---------+------+--------+---------------------------------+
1 row in set (0.00 sec)
Maybe your mysql version? Tested this on 5.5.
It's hard to optimize this query (remove the using filesort and temporary) without knowing the full table structure and what you want to retrieve with this (specify the fields instead of "*")
I'm trying to run the following query in my database:
SELECT * FROM ts_cards WHERE ( cardstatus= 2 OR cardstatus= 3 ) AND ( cardtype= 1 OR cardtype= 2 ) ORDER BY cardserial DESC LIMIT 10;
All three fields (cardstatus, cardtype and cardserial) are indexed:
mysql> SHOW INDEX FROM ts_cards;
+----------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+----------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
| ts_cards | 0 | PRIMARY | 1 | card_id | A | 15000134 | NULL | NULL | | BTREE | |
| ts_cards | 1 | CardID | 1 | cardserial | A | 15000134 | NULL | NULL | | BTREE | |
| ts_cards | 1 | CardType | 1 | cardtype | A | 17 | NULL | NULL | | BTREE | |
| ts_cards | 1 | CardHolder | 1 | cardstatusholder | A | 17 | NULL | NULL | | BTREE | |
| ts_cards | 1 | CardExpiration | 1 | cardexpiredstatus | A | 17 | NULL | NULL | | BTREE | |
| ts_cards | 1 | CardStatus | 1 | cardstatus | A | 17 | NULL | NULL | | BTREE | |
+----------+------------+----------------+--------------+-------------------+-----------+-------------+----------+--------+------+------------+---------+
6 rows in set (0.22 sec)
(Yes, I know the index's names suck)
However, by default, MySQL uses only cardstatus' index:
mysql> EXPLAIN SELECT * FROM `ts_cards` WHERE ( cardstatus= 2 OR cardstatus= 3 ) AND ( cardtype= 1 OR cardtype= 2 ) ORDER BY cardserial DESC LIMIT 10;
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
| 1 | SIMPLE | ts_cards | range | CardType,CardStatus | CardStatus | 1 | NULL | 3215967 | Using where; Using filesort |
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)
(It doesn't even consider the index on cardserial but I guess that's another problem.)
Using "USE KEY" or "FORCE KEY" can make it use cardtype's index, but not both cardtype and cardstatus:
mysql> EXPLAIN SELECT * FROM `ts_cards` FORCE KEY (CardType) WHERE ( cardstatus= 2 OR cardstatus= 3 ) AND ( cardtype= 1 OR cardtype= 2 ) ORDER BY cardserial DESC LIMIT 10;
+----+-------------+----------+-------+---------------+----------+---------+------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+----------+---------+------+---------+-----------------------------+
| 1 | SIMPLE | ts_cards | range | CardType | CardType | 1 | NULL | 6084861 | Using where; Using filesort |
+----+-------------+----------+-------+---------------+----------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT * FROM `ts_cards` FORCE KEY (CardType,CardStatus) WHERE ( cardstatus= 2 OR cardstatus= 3 ) AND ( cardtype= 1 OR cardtype= 2 ) ORDER BY cardserial DESC LIMIT 10;
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
| 1 | SIMPLE | ts_cards | range | CardType,CardStatus | CardStatus | 1 | NULL | 3215967 | Using where; Using filesort |
+----+-------------+----------+-------+---------------------+------------+---------+------+---------+-----------------------------+
1 row in set (0.00 sec)
How can I force MySQL to use BOTH indexes to speed up the query? Both cardtype and cardstatus indexes seem to be defined in the same way yet cardstatus seems to take precedence over cardtype.
IIRC, MySQL cannot use two distinct indexes in the same query. To make use of both indexes, MySQL would need to merge them into one (link to manual). Here is an example if such merge (click on "View Execution Plan"). Notice the "index_merge" of the first SELECT.
Disclaimer: I'm not absolutely sure about the above information.
In your case, despite your hints, the optimizer still considers that the direct scanning of the second table is faster than merging indexes (your tables probably have a very large number of rows, hence a very large, costly-to-manipulate index).
I advise:
ALTER TABLE ADD INDEX CardTypeStatus (cardtype, cardstatus);
This creates an index on both columns. Your query will probably be able to use this index. You may want to drop your CardType index afterwards: queries can still use the two-column index even if they search on the cardtype column only (but not if they search on cardstatus only).
More information about multiple-column indexes: http://dev.mysql.com/doc/refman/5.5/en/multiple-column-indexes.html
Is there a difference between the following two statements:
mysql> EXPLAIN SELECT IF(arms IS NULL, 'asdf', arms) FROM limbs;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | limbs | ALL | NULL | NULL | NULL | NULL | 12 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT IF(arms IS NULL, 'asdf', arms) FROM limbs;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | limbs | ALL | NULL | NULL | NULL | NULL | 12 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
1 row in set (0.00 sec)
Does one perform better or is preferable over the other? Or are they identical?
IFNULL is just a syntactic sugar for IF, which is a syntactic sugar for ANSI SQL CASE.
So use whichever you like better and whichever fits your particular needs.
PS: EXPLAIN doesn't rely on SELECT