Avatar billede egeriis Nybegynder
03. november 2008 - 22:13 Der er 5 kommentarer

Optimering af Join query

Hej,

Jeg håber du kan hjælpe mig!

Jeg har et optimeringsproblem i en løsning jeg arbejder på. Ved explain, kan jeg se at jeg på tabellen 'a' har flg.:

type=index, possible_keys=NULL, ref=NULL, rows=671992

Hvilket ikke virker optimalt. Jeg har forsøgt at gennemgå MySQL's 'Optimizing with EXPLAIN' og deres 'Optimizing with JOIN' grundigt, men jeg synes ikke at gøre mig klogere.

Her er mit query:

SELECT
    `a`.`ad_id`,
    COUNT(DISTINCT `kwhere`.`keyword_id`),
    COUNT(DISTINCT `kwho`.`keyword_id`)
FROM
    `test_ads` as `a`

LEFT JOIN
    `test_ad_keys_where` `where`
ON
    `where`.`ad_id`=`a`.`ad_id`

LEFT JOIN
    `test_ad_keys_who` `who`
ON
    `who`.`ad_id`=`a`.`ad_id`

LEFT JOIN
    `test_keywords` `kwhere`
ON
    `kwhere`.`keyword_id`=`where`.`keyword_id` AND `kwhere`.`keyword` LIKE 'køb%'

LEFT JOIN
    `test_keywords` `kwho`
ON
    `kwho`.`keyword_id`=`who`.`keyword_id` AND `kwho`.`keyword` LIKE 'invest%'

GROUP BY
    `a`.`ad_id`

HAVING
    COUNT(DISTINCT `kwhere`.`keyword_id`) > 0
AND
    COUNT(DISTINCT `kwho`.`keyword_id`) > 0


En kort forklaring:
De to 'test_ad_keys_*' tabeller indeholder relationer mellem keywords og ads.

Struktur for ads tabellen:

CREATE TABLE `test_ads` (
  `ad_id` int(11) NOT NULL auto_increment,
  `customer_id` int(11) NOT NULL default '0',
  `order_id` int(11) NOT NULL default '0',
  `type` tinyint(4) NOT NULL default '0',
  `link_id` int(11) NOT NULL default '0',
  PRIMARY KEY  (`ad_id`),
  KEY `customer_id` (`customer_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_danish_ci;

Struktur for test_ad_keys_* tabellerne:

CREATE TABLE `test_ad_keys_who` (
  `ad_id` int(11) NOT NULL default '0',
  `keyword_id` int(11) NOT NULL default '0',
  PRIMARY KEY  (`ad_id`,`keyword_id`),
  KEY `keyword_id` (`keyword_id`),
  KEY `ad_id` (`ad_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Struktur for test_keywords tabellen:

CREATE TABLE test_keywords (
  keyword_id int(11) NOT NULL auto_increment,
  keyword varchar(50) NOT NULL default '',
  PRIMARY KEY  (keyword_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Avatar billede kjulius Novice
03. november 2008 - 22:43 #1
Bare et forslag (jeg aner ærligt talt ikke om det ændrer noget):

Da HAVING tester på, om der er nogle 'keyword_id' og disse er sammenknytningskriteriet, burde du kunne filtrere allerede på et tidligere tidspunkt, altså før grupperingen ved at ændre til en INNER JOIN. Disse Id felter vil jo kun være tælbare hvis de ikke er null. Da begge tællere skal være > 0 er der for mig at se ingen grund til at bruge LEFT JOINs og gruppere på en masse rækker, som alligevel senere bliver filtreret fra i HAVING segmentet.

Mit utestede forslag ville derfor være:

SELECT
    `a`.`ad_id`,
    COUNT(DISTINCT `kwhere`.`keyword_id`),
    COUNT(DISTINCT `kwho`.`keyword_id`)
FROM
    `test_ads` as `a`

INNER JOIN
    `test_ad_keys_where` `where`
ON
    `where`.`ad_id`=`a`.`ad_id`

INNER JOIN
    `test_ad_keys_who` `who`
ON
    `who`.`ad_id`=`a`.`ad_id`

INNER JOIN
    `test_keywords` `kwhere`
ON
    `kwhere`.`keyword_id`=`where`.`keyword_id` AND `kwhere`.`keyword` LIKE 'køb%'

INNER JOIN
    `test_keywords` `kwho`
ON
    `kwho`.`keyword_id`=`who`.`keyword_id` AND `kwho`.`keyword` LIKE 'invest%'

GROUP BY
    `a`.`ad_id`

Det er som sagt rent "hjernespin", så måske er jeg helt forkert på den, og så kan du jo bare ignorere mit indfald.
Avatar billede kjulius Novice
03. november 2008 - 22:47 #2
Det ville ydermere være nærliggende at flytte LIKE filtreringen til WHERE delen, nu da det er ændret til INNER JOINS:

SELECT
    `a`.`ad_id`,
    COUNT(DISTINCT `kwhere`.`keyword_id`),
    COUNT(DISTINCT `kwho`.`keyword_id`)
FROM
    `test_ads` as `a`

INNER JOIN
    `test_ad_keys_where` `where`
ON
    `where`.`ad_id`=`a`.`ad_id`

INNER JOIN
    `test_ad_keys_who` `who`
ON
    `who`.`ad_id`=`a`.`ad_id`

INNER JOIN
    `test_keywords` `kwhere`
ON
    `kwhere`.`keyword_id`=`where`.`keyword_id`
INNER JOIN
    `test_keywords` `kwho`
ON
    `kwho`.`keyword_id`=`who`.`keyword_id`

WHERE `kwhere`.`keyword` LIKE 'køb%'
  AND `kwho`.`keyword` LIKE 'invest%'


GROUP BY
    `a`.`ad_id`
Avatar billede egeriis Nybegynder
04. november 2008 - 23:36 #3
Det var ihvertfald en væsentlig optimering :-) Men jeg er nu stadig ikke tilfreds. Jeg har for mange records til at det er hurtigt nok. EXPLAIN afslører med mit eget query en gennemsøgning af 6.050.000 records, mod 5.300.000 med dit.

Kan du foreslå en yderligere forbedring?

Det jeg selv ser som en mulighed, jeg ikke ved om har nogen reel værdi, er at undlade at joine ads tabellen i starten. Dvs. lave et subquery, som henter ad_ids, hvorefter jeg joiner dette subquery som en derived table med ads tabellen.

Er det en ide?
Avatar billede egeriis Nybegynder
04. november 2008 - 23:37 #4
Det er i øvrigt ikke nødvendigt med denne COUNT i SELECT. Den brugte jeg i mit eget query til at se om der var nogle resultater. Din løsning er i dette tilfælde MEGET bedre!
Avatar billede egeriis Nybegynder
05. november 2008 - 00:01 #5
En anden idé jeg har, er at omrokere queriet, så ads-tabellen ikke er den som står efter FROM. Den tabel jeg forventer mindst rækker fra, hvorfor jeg vil mene at den er den bedste at gå ud fra, er kwhere og kwho. Men da der er to, kan jeg simpelthen ikke gennemskue hvordan og hvorledes.

Håber at du, eller en anden, kan hjælpe :-)
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Computerworld tilbyder specialiserede kurser i database-management

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester