Optimize MySql Query? - mysql

I have the following query in an old database (MySql 5.7.16) that takes almost 45 seconds to run.
The table tbl_flightno has some 5 million records, the tbl_airline around 12,000. It seems the database is a bit at the limit, and every now and then there are some orphan records generated. I haven't found the culprit for that yet.
So I'm currently checking every now and then for those orphans and then fix them. I am wondering now, if there is a better way to search for those orphans.
SELECT COUNT(DISTINCT N.World, N.AirlineCode) AS 'Orphans', COUNT(FlightNoID) AS 'Flights'
FROM tbl_flightno N
LEFT JOIN tbl_airline A ON A.World = N.World AND A.AirlineCode = N.AirlineCode
WHERE A.Airline IS NULL
However I'm not sure there is another, or better way.
Yes, updating the MySql version might benefit, also throwing more hardware would improve, but that would create much more work.
Thanks in advance for any hints.
EDIT: Added the additional information below:
Here is the EXPLAIN for the query.
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE N index World_Airline 81 5217525 100 Using index
1 SIMPLE A eq_ref PRIMARY,VUnique,vWorld,vAirline,vReadOnly PRIMARY 81 as.N.AirlineCode,as.N.World 1 10 Using where; Not exists
-- ----------------------------
-- Table structure for tbl_airline
-- ----------------------------
DROP TABLE IF EXISTS `tbl_airline`;
CREATE TABLE `tbl_airline` (
`AirlineCode` int(8) NOT NULL,
`World` varchar(25) NOT NULL,
`Airline` varchar(255) NOT NULL,
`Last_update` datetime DEFAULT NULL,
`Destinations` int(8) DEFAULT NULL,
`NoFlights` int(8) DEFAULT NULL,
`CityPairs` int(8) DEFAULT NULL,
`Headquarter` varchar(3) DEFAULT NULL,
`TZ` varchar(6) DEFAULT NULL,
`ReadOnly` int(1) NOT NULL DEFAULT '0',
`Code` varchar(10) DEFAULT NULL,
`Alliance` varchar(255) DEFAULT NULL,
`Stock` varchar(10) DEFAULT NULL,
`Country` varchar(255) DEFAULT NULL,
`LegalHome` varchar(255) DEFAULT NULL,
`Parent` varchar(255) DEFAULT NULL,
`Director` varchar(100) DEFAULT NULL,
`Founded` date DEFAULT NULL,
`Rating` varchar(5) DEFAULT NULL,
PRIMARY KEY (`AirlineCode`,`World`),
UNIQUE KEY `VUnique` (`World`,`AirlineCode`) USING BTREE,
KEY `vWorld` (`World`) USING BTREE,
KEY `vAirline` (`AirlineCode`) USING BTREE,
KEY `vReadOnly` (`World`,`ReadOnly`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=1;
-- ----------------------------
-- Table structure for tbl_flightno
-- ----------------------------
DROP TABLE IF EXISTS `tbl_flightno`;
CREATE TABLE `tbl_flightno` (
`FlightNoID` bigint(8) unsigned NOT NULL AUTO_INCREMENT,
`FlightID` bigint(8) unsigned NOT NULL,
`World` varchar(25) NOT NULL,
`AirlineCode` int(8) NOT NULL,
`FlightNo` varchar(10) NOT NULL,
`Days` varchar(7) NOT NULL,
`TimeDep` time NOT NULL,
`TimeArr` time NOT NULL,
`ActType` varchar(3) NOT NULL,
`ActLink` varchar(6) NOT NULL,
`Operator` varchar(255) NOT NULL,
`Remarks` varchar(50) DEFAULT NULL,
`Validity` varchar(11) DEFAULT NULL,
`Distance` int(10) DEFAULT NULL,
`Duration` time DEFAULT NULL,
`Speed` int(10) DEFAULT NULL,
`Via` int(1) DEFAULT '0',
`AptFromC` varchar(3) DEFAULT NULL,
`AptDestC` varchar(3) DEFAULT NULL,
PRIMARY KEY (`FlightNoID`),
UNIQUE KEY `FlightNoID` (`FlightNoID`) USING BTREE,
KEY `World_Airline` (`World`,`AirlineCode`) USING BTREE,
KEY `DepTimes` (`TimeDep`,`FlightID`) USING BTREE,
KEY `FlightID` (`FlightID`) USING BTREE,
KEY `Distance` (`World`,`AirlineCode`,`Distance`) USING BTREE,
KEY `ActType` (`ActType`) USING BTREE,
KEY `Via` (`Via`) USING BTREE,
KEY `Remarks` (`World`,`Remarks`) USING BTREE,
KEY `ActLink` (`ActLink`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=25879501 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS=1;

You can try the following query:
SELECT COUNT(DISTINCT N.World, N.AirlineCode) AS Orphans
, COUNT(CASE WHEN NOT EXISTS (
SELECT 1 FROM tbl_airline A
WHERE A.World = N.World
AND A.AirlineCode = N.AirlineCode
) THEN 1
END) AS Flights
FROM tbl_flightno N;

Your indexes are optimal and your query formulation is optimal. The problem is that it needs 5M * 12K checks.
If it is I/O bound then, please provide table sizes and value of innodb_buffer_pool_size and the size of RAM. with these, I may have advice on how to cut back on I/O.
[An aside] There are several redundant indexes, but this won't impact the speed of that SELECT
PRIMARY KEY (`AirlineCode`,`World`),
UNIQUE KEY `VUnique` (`World`,`AirlineCode`) USING BTREE,
KEY `vWorld` (`World`) USING BTREE,
KEY `vAirline` (`AirlineCode`) USING BTREE,
KEY `vReadOnly` (`World`,`ReadOnly`) USING BTREE
-->
PRIMARY KEY (`AirlineCode`,`World`),
KEY (`World`,`AirlineCode`) USING BTREE,
KEY `vReadOnly` (`World`,`ReadOnly`) USING BTREE
In the other table, toss these two:
UNIQUE KEY `FlightNoID` (`FlightNoID`) USING BTREE,
KEY `World_Airline` (`World`,`AirlineCode`) USING BTREE,
"Rules":
In MySQL PRIMARY KEY is a UNIQUE key.
When you have INDEX(a,b), INDEX(a) is unnecessary.

Related

MySQL Performance issue: select query takes forever

This is vitally important query of the project that calculates statistic information for each user, generates almost 300k rows but it takes forever to get answer.
The problem is, this query is needed to be executed almost every 20-30 seconds and the row count always grows.
Despite the fact that the fields that used in join and where are indexed it takes around 2000 seconds to get the answer from query.
One another fact is, the tables used in this query are large. For example, IconKeyword holds almost 95M records.
Please take a look and tell me how can I optimize this query
SELECT
Icon.`user_id` AS user_id,
Keyword.`id` AS keyword_id,
Keyword.`title` AS keyword_title,
Keyword.`demand` AS keyword_demand,
count( IconKeyword.`iconID` ) AS ico_count,
Icon.`type` AS icon_type,
Keyword.`common` AS common
FROM
Icon
INNER JOIN IconKeyword ON IconKeyword.iconID = Icon.id
INNER JOIN Keyword ON Keyword.id = IconKeyword.keywordID
WHERE
Keyword.is_deleted = 0
AND
Keyword.restricted = 0
GROUP BY
Icon.`user_id`,
Icon.type,
Keyword.id;
And here is explain results:
Create tables:
CREATE TABLE `Icon` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`description` text CHARACTER SET utf8 COLLATE utf8_general_ci,
`type` tinyint DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`user_id` int unsigned DEFAULT NULL,
`f_id` int unsigned DEFAULT NULL,
`color` tinyint unsigned DEFAULT '0',
`pack_id` int unsigned DEFAULT NULL,
`pack_bg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`fi_url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`is_deleted` tinyint unsigned DEFAULT '0',
`count_of_sets` int unsigned NOT NULL DEFAULT '0',
`p_hash` binary(64) DEFAULT NULL,
`hash_bit_count` int unsigned GENERATED ALWAYS AS (bit_count(`p_hash`)) STORED,
`checked` tinyint unsigned DEFAULT '0',
`md5` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `f_id` (`f_id`) USING BTREE,
KEY `user_id` (`user_id`) USING BTREE,
KEY `id` (`id`,`type`,`user_id`) USING BTREE,
KEY `type` (`type`) USING BTREE,
KEY `uri` (`uri`) USING BTREE,
KEY `hash_bit_count` (`hash_bit_count`) USING BTREE,
KEY `is_deleted` (`is_deleted`),
KEY `checked` (`checked`),
KEY `user_id_2` (`user_id`,`type`) USING BTREE,
CONSTRAINT `Icon_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `User` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=118691 DEFAULT CHARSET=utf8
CREATE TABLE `Keyword` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`slug` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`common` tinyint unsigned DEFAULT '0',
`demand` int DEFAULT '0',
`f_id` int unsigned DEFAULT NULL,
`is_deleted` tinyint unsigned DEFAULT '0',
`needs_review` tinyint unsigned DEFAULT '0',
`checked` tinyint unsigned DEFAULT '0',
`restricted` tinyint unsigned DEFAULT '0',
`word_checked` tinyint unsigned DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `slug` (`slug`) USING BTREE,
KEY `title` (`title`) USING BTREE,
KEY `is_deleted` (`is_deleted`) USING BTREE,
KEY `restricted` (`restricted`) USING BTREE,
KEY `restricted_2` (`restricted`,`is_deleted`,`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=30000392 DEFAULT CHARSET=utf8
CREATE TABLE `IconKeyword` (
`iconID` int unsigned NOT NULL,
`keywordID` int unsigned NOT NULL,
PRIMARY KEY (`iconID`,`keywordID`) USING BTREE,
UNIQUE KEY `keywordID_2` (`keywordID`,`iconID`),
KEY `keywordID` (`keywordID`) USING BTREE,
KEY `iconID` (`iconID`) USING BTREE,
CONSTRAINT `ik_ibfk_1` FOREIGN KEY (`keywordID`) REFERENCES `Keyword` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `ik_ibfk_2` FOREIGN KEY (`iconID`) REFERENCES `Icon` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
For your query, you can try adding indexes on:
keyword(is_deleted, is_restricted, id)
IconKeyword(KeywordId, IconId)
Icon(id) (which should already exist because id is a primary key)
The query will still need to do a lot of work for the aggregation, but this might help.
I guess the number of records that have Keyword.is_deleted = 0 AND Keyword.restricted = 0 is quite large, so adding indexes here wouldn't help much.
What you can try is helping the aggregation works faster by adding a Multiple-Column Indexes, specifically (Icon.user_id, Icon.type) because it's what you're using in GROUP BY
Also, can you provide an EXPLAIN result of this query so we can better understand what is causing the performance problem.

MariaDB INNER JOIN with foreign keys are MUCH slower than without them

Please help me, I'm stuck with the strange behaviour of MariaDB server.
I have 3 tables.
CREATE TABLE `default_work` (
`add_date` datetime(6) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`keywords` varchar(255) DEFAULT NULL,
`short_text` longtext DEFAULT NULL,
`downloads` int(10) unsigned NOT NULL,
`published` tinyint(1) NOT NULL,
`subject_id` int(11) NOT NULL,
`work_type_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `default_work_subject_id_IDX` (`subject_id`) USING BTREE,
KEY `default_work_work_type_id_IDX` (`work_type_id`) USING BTREE,
CONSTRAINT `default_work_FK` FOREIGN KEY (`subject_id`) REFERENCES `default_subject` (`id`),
CONSTRAINT `default_work_FK_1` FOREIGN KEY (`work_type_id`) REFERENCES `default_worktype` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=210673 DEFAULT CHARSET=utf8
CREATE TABLE `default_subject` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subject` varchar(255) NOT NULL,
`old_id` int(10) unsigned NOT NULL,
`subject_literal` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8
CREATE TABLE `default_worktype` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`work_type` varchar(250) NOT NULL,
`description` longtext DEFAULT NULL,
`old_id` int(10) unsigned NOT NULL,
`work_type_literal` varchar(250) NOT NULL,
`title` varchar(255) NOT NULL,
`multiple` varchar(255) NOT NULL,
`keywords` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `default_worktype_old_id_a8b508fe_uniq` (`old_id`),
UNIQUE KEY `default_worktype_work_type_literal_1e609434_uniq` (`work_type_literal`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8
These tables were created by Django ORM but it seems to be ok.
The default_work table has about 200,000 records, default_subject - 42, and default_worktype - 12.
After I was making a request in Django admin with simple joins between those tables I've got about 9 secs of query time.
Looked in SQL log I've found a raw query:
SELECT `default_work`.`id`, `default_work`.`title`, `default_worktype`.`work_type`,`default_subject`.`subject`
FROM `default_work`
INNER JOIN `default_subject` ON (`default_work`.`subject_id` = `default_subject`.`id`)
INNER JOIN `default_worktype` ON (`default_work`.`work_type_id` = `default_worktype`.`id`)
ORDER BY `default_work`.`id` DESC LIMIT 100
The explain showing:
Explain result of the query with indexes
And this is a bit confusing because when I deleted all indexes on table default_work except the primary key, the results were completely different. The request time was about 3.4 msec and explain shows the all primary keys are used correctly.
Explain result of the query without indexes
PS. I'm tried to reproduce this situation on PostgreSQL and got a 1.3 msec with the request with indexes and foreign keys.
Looking at your EXPLAIN results you can see that when the foreign keys are turned on the system is using that key in the join, instead of choosing to use the primary key in the target table. (row 2)
As there will be many records with the same value it massivley increases the records that are being evaluated.
I don't know why it's choosing to do that. You may find that rewriting the select statement in a different order will change how it chooses the indexes. You may find the choice is different if in the ON clause you secify target table first then the source table (default_subject.id = default_work.subject_id)

MYSQL INNER JOIN is slow with index

this is my simple inner join:
SELECT
SUM(ASSNZ.assenzeDidattiche) AS TotaleAssenze,
SUM(ASSNZ.ore) AS totale_parziale,
FLOOR(((SUM(ASSNZ.assenzeDidattiche) / SUM(ASSNZ.ore)) * 100)) AS andamento,
MAX(ASSNZ.dataLezione) AS ultima_lezione,
ASSNZ.idServizio,
ASSNZ.idUtente
FROM
ciac_corsi_assenze AS ASSNZ
INNER JOIN
ciac_serviziAcquistati_ITA AS ACQ
ON ACQ.idContatto = ASSNZ.idUtente
AND ACQ.idServizio = ASSNZ.idServizio
AND ACQ.stato_allievo <> 'ritirato'
GROUP BY
ASSNZ.idServizio,
ASSNZ.idUtente
table "ASSNZ" has 213886 rows with index "idUtente", "idServizio"
table "ACQ" has 8950 rows with index "idContatto", "idServizio"
ASSNZ table:
CREATE TABLE `ciac_corsi_assenze` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`idUtente` int(11) DEFAULT NULL,
`idServizio` int(11) DEFAULT NULL,
`idCorso` int(11) DEFAULT NULL,
`idCalendario` int(11) DEFAULT NULL,
`modalita` varchar(255) DEFAULT NULL,
`ore` int(11) DEFAULT NULL,
`assenzeDidattiche` float DEFAULT NULL,
`assenzeAmministrative` float DEFAULT NULL,
`dataLezione` date DEFAULT NULL,
`ora_inizio` varchar(8) DEFAULT NULL,
`ora_fine` varchar(8) DEFAULT NULL,
`dataFineStage` date DEFAULT NULL,
`giustificata` varchar(128) DEFAULT NULL,
`motivazione` longtext,
`grouped` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idUtente` (`idUtente`) USING BTREE,
KEY `idServizio` (`idServizio`) USING BTREE,
KEY `dataLezione` (`dataLezione`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=574582 DEFAULT CHARSET=utf8;
ACQ table:
CREATE TABLE `ciac_serviziacquistati_ita` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`idServizio` int(11) NOT NULL,
`idContatto` int(11) NOT NULL,
`idAzienda` int(11) NOT NULL,
`idSede` int(11) NOT NULL,
`tipoPersona` int(11) NOT NULL,
`num_registro` int(11) NOT NULL,
`codice` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
`dal` date NOT NULL,
`al` date NOT NULL,
`ore` int(11) NOT NULL,
`costoOrario` decimal(10,0) NOT NULL,
`annoFormativo` varchar(128) CHARACTER SET latin1 NOT NULL,
`stato_attuale` int(11) NOT NULL,
`datore_attuale` int(11) NOT NULL,
`stato_allievo` varchar(64) CHARACTER SET latin1 NOT NULL DEFAULT 'corsista',
`data_ritiro` date DEFAULT NULL,
`crediti_formativi` int(11) NOT NULL,
`note` longtext CHARACTER SET latin1 NOT NULL,
`valore_economico` decimal(10,2) NOT NULL,
`dataInserimento` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idServizio` (`idServizio`) USING BTREE,
KEY `idAzienda` (`idAzienda`) USING BTREE,
KEY `idContatto` (`idContatto`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9542 DEFAULT CHARSET=utf8;
this is my EXPLAIN of the select
Now because the query is slow, during 1.5s / 2.0s??
Something wrong?
UPDATE
added new index (with the John Bollinger's answer) to the table ciac_corsi_assenze:
PRIMARY KEY (`id`),
KEY `dataLezione` (`dataLezione`) USING BTREE,
KEY `test` (`idUtente`,`idServizio`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=574582 DEFAULT CHARSET=utf8;
added new index to the table ciac_serviziAcquistati_ITA:
PRIMARY KEY (`id`),
KEY `idAzienda` (`idAzienda`) USING BTREE,
KEY `test2` (`idContatto`,`idServizio`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9542 DEFAULT CHARSET=utf8;
New EXPLAIN:
But it's always slow :(
Your tables have separate indexes on various columns of interest, but MySQL will use at most one index per table to perform your query. This particular query would probably be sped by table ciac_corsi_assenze having an index on (idUtente, idServizio) (and such an index would supersede the existing one on (idUtente) alone). That should allow MySQL to avoid sorting the result rows to perform the grouping, and it will help more in performing the join than any of the existing indexes do.
The query would probably be sped further by table ciac_serviziAcquistati_ITA having an index on (idContatto, idServizio), or even on (idContatto, idServizio, ritirato). Either of those would supersede the existing index on just (idContatto).
John went the right direction. However the order of columns in the composite index needs changing.
For the GROUP BY, this order is needed (on ASSNZ):
INDEX(idServizio, idUtente)
(and that should replace KEY(idServizio), but not KEY(idUtente))
Then ACQ needs, in this order:
INDEX(idContatto, idServizio, stato_allievo)
replacing only KEY(idContatto).

MySQL Sorting bad performance

I see the following (simplified) query appearing in my slow-log mysql file, it's by a search engine crawler that crawls all pages from a paginated website.
select * from listing
where
active=1
and approved=1
and deleted=0
and enabled=1
order by
full_listing desc,
picture_count*rating/rating_count desc,
rating desc
limit 21230, 10;
I'm surprised it takes over 8 seconds to process on a table of only 60,000 records.
The explain plan looks like this
1 SIMPLE listing0_ index_merge listing_active,listing_approved,listing_enabled,listing_deleted,sort_index listing_active,listing_approved,listing_enabled,listing_deleted 1,1,1,1 3102 Using intersect(listing_active,listing_approved,listing_enabled,listing_deleted); Using where; Using filesort
What index can I create in order to improve its performance?
table structure:
'CREATE TABLE `listing` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`account_id` bigint(20) DEFAULT NULL,
`domain_id` bigint(20) DEFAULT NULL,
`active` bit(1) NOT NULL,
`approved` bit(1) NOT NULL,
`denied` bit(1) NOT NULL,
`deleted` bit(1) NOT NULL,
`enabled` bit(1) NOT NULL,
`full_listing` bit(1) DEFAULT b''0'',
`public_id` varchar(255) DEFAULT NULL,
`name` varchar(100) NOT NULL,
`rating` int(11) NOT NULL,
`rating_count` int(11) NOT NULL,
`rating_enabled` bit(1) NOT NULL,
`picture_count` int(11) DEFAULT ''0'',
`createdAt` datetime NOT NULL,
`createdBy` varchar(15) NOT NULL,
`updatedAt` datetime NOT NULL,
`updatedBy` varchar(15) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `listing_public_id` (`public_id`),
KEY `FKB4DC521D9306A80C` (`account_id`),
KEY `FKB4DC522D7A66E1A8` (`domain_id`),
KEY `listing_active` (`active`),
KEY `listing_approved` (`approved`),
KEY `listing_enabled` (`enabled`),
KEY `listing_deleted` (`deleted`),
KEY `listing_picture_count` (`picture_count`),
KEY `listing_rating` (`rating`),
KEY `listing_rating_count` (`rating_count`),
KEY `listing_full_listing` (`full_listing`),
CONSTRAINT `listing_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`),
CONSTRAINT `listing_ibfk_2` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=59512 DEFAULT CHARSET=utf8'
Using filesort could be a cause. What are the index's ? Personally i would use bitshifts for what you are doing for for example column status contains an int
with the right hand bits as this
0000
Active | Approved | Deleted | enabled
so in your example I would have where status = 1011
or status = 11 (when the bits are converted back in to ints)
Then you only have 1 index with all combinations (using 0 and 1 can sometimes make mysql ignore the index)

Why do i HAVE to optimize tables?

I have a pretty big table with contains about 3 million records.
When running a very simple query, joining this table on a few others (all with indexes and/or primary keys), the query will take about 25 seconds to complete!
The value of "Handler_read_next" is about 7 million!
Number of requests to read the next row in key order, incremented if you are querying an index column with a range constraint or if you are doing an index scan.
This problem have only started since this table began to grow big.
Now if I do an "optimize tables" on this table, the query will run in about 0.02 seconds and "Handler_read_next" will have a value of about 1500.
How can the difference be so extreme, and do I really have to setup a scheduled query, optimizing this table once a week or so? Even so, I would like to know the meaning behind this and why mysql behaves like this. Sure, rows are deleted and updated pretty much in this table, but should it get so badly fragmented in only one week that the query goes from 0.02 sec to 25 sec?
Edit: After request, here comes the query in question:
SELECT *
FROM budget_expenses
JOIN budget_categories
ON budget_categories.BudgetAreaId = budget_expenses.BudgetAreaId
AND budget_categories.BudgetCategoryId = budget_expenses.BudgetCategoryId
LEFT JOIN budget_types
ON budget_types.BudgetAreaId = budget_expenses.BudgetAreaId
AND budget_types.BudgetCategoryId = budget_expenses.BudgetCategoryId
AND budget_types.BudgetTypeId = budget_expenses.BudgetTypeId
WHERE budget_expenses.BudgetId = 1
AND budget_expenses.ExpenseDate >= '2012-11-25'
AND budget_expenses.ExpenseDate <= '2012-12-24'
AND budget_expenses.BudgetAreaId = 2
ORDER BY budget_expenses.ExpenseDate DESC,
budget_expenses.ExpenseTime IS NULL ASC,
budget_expenses.ExpenseTime DESC
(BudgetAreaId, BudgetCategoryId) is the primary key in budget_categories and (BudgetAreaId, BudgetCategoryId, BudgetTypeId) is the primary key in budget_types. In budget_expenses these 3 keys are indexes and also ExpenseDate has an index. This query returns about 20 rows.
Show create table:
CREATE TABLE `budget_areas` (
`BudgetAreaId` int(11) NOT NULL,
`Name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`BudgetAreaId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `budget_categories` (
`BudgetAreaId` int(11) NOT NULL,
`BudgetCategoryId` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(255) DEFAULT NULL,
`SortOrder` int(11) DEFAULT NULL,
PRIMARY KEY (`BudgetAreaId`,`BudgetCategoryId`),
KEY `BudgetAreaId` (`BudgetAreaId`,`BudgetCategoryId`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
CREATE TABLE `budget_types` (
`BudgetAreaId` int(11) NOT NULL,
`BudgetCategoryId` int(11) NOT NULL,
`BudgetTypeId` int(11) NOT NULL,
`Name` varchar(255) DEFAULT NULL,
`SortId` int(11) DEFAULT NULL,
PRIMARY KEY (`BudgetAreaId`,`BudgetCategoryId`,`BudgetTypeId`),
KEY `BudgetAreaId` (`BudgetAreaId`,`BudgetCategoryId`,`BudgetTypeId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
CREATE TABLE `budget_expenses` (
`ExpenseId` int(11) NOT NULL AUTO_INCREMENT,
`BudgetId` int(11) NOT NULL,
`TempId` int(11) DEFAULT NULL,
`BudgetAreaId` int(11) DEFAULT NULL,
`BudgetCategoryId` int(11) DEFAULT NULL,
`BudgetTypeId` int(11) DEFAULT NULL,
`Company` varchar(255) DEFAULT NULL,
`ImportCompany` varchar(255) DEFAULT NULL,
`Sum` double(50,2) DEFAULT NULL,
`ExpenseDate` date DEFAULT NULL,
`ExpenseTime` time DEFAULT NULL,
`Inserted` datetime DEFAULT NULL,
`Changed` datetime DEFAULT NULL,
`InsertType` int(1) DEFAULT NULL,
`AccountId` int(11) DEFAULT NULL,
`BankCardId` int(11) DEFAULT NULL,
PRIMARY KEY (`ExpenseId`),
KEY `BudgetId` (`BudgetId`),
KEY `AccountId` (`AccountId`),
KEY `Company` (`Company`) USING BTREE,
KEY `ExpenseDate` (`ExpenseDate`),
KEY `BudgetAreaId` (`BudgetAreaId`),
KEY `BudgetCategoryId` (`BudgetCategoryId`),
KEY `BudgetTypeId` (`BudgetTypeId`),
CONSTRAINT `budget_expenses_ibfk_1` FOREIGN KEY (`BudgetId`) REFERENCES `budgets` (`BudgetId`)
) ENGINE=InnoDB AUTO_INCREMENT=3604462 DEFAULT CHARSET=latin1
After I copy pasted this I changed from MyIsam to Innodb on the budget_categories table.
Edit: The change from myisam to innodb didn't make any difference. The query is now very slow, just 12 hours after i optimized the budget_expenses table!
Here is the explain for the query which now takes about 9 seconds:
http://jsfiddle.net/dmVPY/1/
Ahhh MyISAM....
Try changing the table type (aka 'storage engine') to InnoDB instead.
If you do this, make sure innodb_buffer_pool_size in your my.cnf is a sensible value - the default is too small.