【问题标题】:Set Indexes correctly for JOIN + LIKE xyz% query为 JOIN + LIKE xyz% 查询正确设置索引
【发布时间】:2014-07-30 19:12:22
【问题描述】:

EDIT3:真正的问题是:

它现在对Contact 使用复合索引,但EXPLAIN 只列出Address 的fkey 索引,既不列出我的多列索引,也不列出任何其他单列索引。

EXPLAIN EXTENDED SELECT c.*
FROM Contact_Contact c 
LEFT OUTER JOIN Address a ON c.id=a.Contact_id 
WHERE c.deleted<>1 
AND c.external<>1 
AND (
  c.firstName LIKE 'abc%' OR
  c.lastName LIKE 'abc%' OR
  c.Company LIKE 'abc%' OR
  c.label LIKE 'abc%' OR
  a.street LIKE 'abc%' OR
  a.city LIKE 'abc%' OR
  a.region LIKE 'abc%') 
GROUP BY c.id 
ORDER BY c.lastName ASC
LIMIT 30

输出:

# id, select_type, table, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, SIMPLE, c, range, myCompositeContactIdx,firstNameIdx,lastNameIdx, myCompositeContactIdx, 1, , 66241, 100.00, Using where; Using temporary; Using filesort
1, SIMPLE, a, ref, FK_dc6mn93h87cim4yr8nnijj3fa,myCompositeAddressIdx , FK_dc6mn93h87cim4yr8nnijj3fa, 17, c.id, 1, 100.00, Using where

FK_dc6mn93h87cim4yr8nnijj3faAddress.Contact_id 上的 fkey)

所以看起来它使用了Contact上的多列索引,但它没有使用Address上的多列索引,定义为:

ADD INDEX `myCompositeAddressIdx` USING BTREE (
  `Contact_id` ASC, `street`(255) ASC, `city`(255) ASC, `region`(255) ASC
);

知道如何使用myCompositeAddressIdx吗?


EDIT4:修改后的查询,速度提高 30%,子查询上没有索引:

这是我的新查询,需要 250 毫秒(旧:350 毫秒)

SELECT c.*
FROM Contact_Contact c
WHERE c.deleted<>1 
AND c.external<>1 
AND (
  c.firstName LIKE 'abc%' OR
  c.lastName LIKE 'abc%' OR
  c.Company LIKE 'abc%' OR
  c.label LIKE 'abc%' OR
  c.id IN (SELECT a.Contact_id FROM Address a WHERE a.street LIKE 'abc%' OR a.city LIKE 'abc%' OR a.region LIKE 'abc%')
  -- ALTERNATIVE:
  -- EXISTS (SELECT 1 FROM Address a WHERE a.Contact_id=c.id AND (a.street LIKE 'abc%' OR a.city LIKE 'abc%' OR a.region LIKE 'abc%'))
)
GROUP BY c.id 
ORDER BY c.lastName ASC
LIMIT 30

INEXISTS 似乎同样快)

EXPLAIN 仍然说,它不使用Address 表上的索引:

# id, select_type, table, type, possible_keys, key, key_len, ref, rows, filtered, Extra
1, PRIMARY, c, range, myCompositeContactIdx,firstNameIdx,lastNameIdx, myCompositeContactIdx, 1, , 67138, 100.00, Using where; Using filesort
2, DEPENDENT SUBQUERY, a, index_subquery, FK_dc6mn93h87cim4yr8nnijj3fa,myCompositeAddressIdx,myNEWCompositeAddressIdx, FK_dc6mn93h87cim4yr8nnijj3fa, 17, func, 1, 100.00, Using where

myNEWCompositeAddressIdx 定义为:

ADD INDEX `myNEWCompositeAddressIdx` USING BTREE (
  `street`(255) ASC, `city`(255) ASC, `region`(255) ASC
);

原问题:

我们的 MySQL 数据库存在一些性能问题,架构如下所示:

TABLE Contact:
id, firstName, lastName, company

TABLE Address:
id, contact_id, street, city

TABLE ContactGroup:
id, name

TABLE Contact_ContactGroup:
contact_id, contact_group_id

(还有一些表格,但总是一样的) (所有文本字段都是 VARCHAR(最多 255 个))

我们的查询类似于输入 "abc xyz"

SELECT c.*
FROM Contact c
LEFT JOIN Address a ON a.contact_id = c.id
LEFT JOIN Contact_ContactGroup ccg ON ccg.contact_id = c.id
WHERE
  (
    c.firstName LIKE 'abc%' OR c.firstName LIKE 'xyz%' OR
    c.lastName LIKE 'abc%' OR c.lastName LIKE 'xyz%' OR
    c.company LIKE 'abc%' OR c.company LIKE 'xyz%' OR
    a.street LIKE 'abc%' OR a.street LIKE 'xyz%' OR
    a.city LIKE 'abc%' OR a.city LIKE 'xyz%'
  ) AND
  ccg.contact_group_id IN (1, 2, 3)
GROUP BY
  c.id
ORDER BY
  c.lastName ASC

在我的开发机器上,大约需要 280 毫秒,联系人表中有大约 130,000 行。 (我们的服务器需要超过 2 秒)

在我们使用DISTINCT 而不是GROUP BY 之前,但它的性能似乎几乎相同。之前我们也有LIKE '%abc%',但我读到如果搜索词前面有通配符,MySQL 就不能使用索引。查询耗时约 310 毫秒

如您所见,查询并没有明显加快。我想我确实设置了错误的索引。我设置了什么:

Contact Index: primary key
id

Contact Index: BTREE
firstName ASC, lastName ASC, company ASC

Address Index: primary key
id

Address Index: BTREE
street ASC, city ASC

Contact_ContactGroup: compound primary key
contact_id, contact_group_id

所有数据已经​​在表中之后添加了索引。 MySQL 是否自动索引旧数据?添加索引的时间不超过半秒,但我不确定它是否真的对数据进行了索引。而且我找不到强制重新索引表的命令。

或者对每一列使用单列索引而不是对所有列使用巨大的复合索引更好?

顺便说一句:我们正在使用 MySQL 5.5 和 InnoDB 表。

任何帮助将不胜感激。谢谢:)

【问题讨论】:

  • LIKE '%anything ...' 根本不能使用索引。考虑使用全文索引。
  • 我知道,这就是为什么我们现在只有anything%。更正了标题,抱歉...顺便说一句:MySQL 的 MATCH AGAINST 也只支持末尾的通配符:anything* 有效,*anything 无效。
  • 我确定您的 WHERE 子句不是您想要的。您想将所有 OR 表达式放在括号中:(c.firstName LIKE 'abc%' OR c.firstName LIKE 'xyz%' OR c.lastName LIKE 'abc%' OR c.lastName LIKE 'xyz%' OR c.company LIKE 'abc%' OR c.company LIKE 'xyz%' OR a.street LIKE 'abc%' OR a.street LIKE 'xyz%' OR a.city LIKE 'abc%' OR a.city LIKE 'xyz%') AND ccg.contact_group_id IN (1, 2, 3),因为 AND 的优先级高于 OR。
  • 我刚刚写下了那个查询,因为我不想从我们的应用程序中复制和粘贴 50 行 SQL。你是对的,我错过了括号。我会补充的。
  • 什么时候创建索引都没关系。所有行都被索引。由于您在 WHERE 子句中引用了 ccg,因此 MySQL 将其视为内部连接。使用 INNER JOIN,MySQL 更喜欢从具有最高选择性(行数较少)的表开始。这是ccg吗?也许。优化这个查询有很多挑战,如果没有更多信息,例如每个表中的行数、数据的一些概念和 EXPLAIN 结果,我们只能猜测。您可以先使用 UNION 而不是 OR。

标签: mysql indexing query-optimization database-performance query-performance


【解决方案1】:

它是否取决于系统,因为这个查询在我的服务器上花费了 180 毫秒, 我碰巧有你的数据库模式的精确副本,而且我在 PL/SQL 过程开发之前也已经索引过。

SELECT c.*
FROM Contact c LEFT JOIN Contact_ContactGroup ccg ON ccg.contact_id = c.id
 LEFT JOIN Address a ON a.contact_id = c.id

WHERE
  c.firstName  LIKE 'abc%xyz%' OR
  c.lastName   LIKE 'abc%xyz%' OR
  c.company   LIKE 'abc%xyz%' OR
  a.street   LIKE 'abc%xyz%' OR
  a.city   LIKE 'abc%xyz%' AND
  ccg.contact_group_id IN (1, 2, 3)
GROUP BY
  c.id
ORDER BY
  c.lastName ASC

【讨论】:

  • 当使用LIKE 'abc%xyz%' 而不是LIKE 'abc%' OR LIKE 'xyz%' 时,这是否真的返回相同的数据。我会尝试一下,但我真的不确定是否等效。
  • 你有多少记录?
  • 对其进行了测试,正如预期的那样,它不会返回相同的结果。 @Sebas:我已经在上面的 cmets 中发布了数据量。
猜你喜欢
  • 1970-01-01
  • 2012-03-21
  • 1970-01-01
  • 2017-11-14
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多