问题描述
CREATE TABLE Articles
(
ArticleID int(11) unsigned NOT NULL AUTO_INCREMENT,Title varchar(255),PRIMARY KEY(ArticleID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
CREATE TABLE Tags
(
TagID int(11) unsigned NOT NULL AUTO_INCREMENT,Tag varchar(255),UNIQUE INDEX(Tag),PRIMARY KEY(TagID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
CREATE TABLE TagMap
(
ArticleID int(11) unsigned NOT NULL,TagID int(11) unsigned NOT NULL,INDEX(TagID),PRIMARY KEY(ArticleID,TagID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
$result = $MysqLi->query("SELECT TagID FROM Tags WHERE Tag='$tag'");
if($result->num_rows == 1) {
$row = $result->fetch_assoc();
$tag_id = $row['TagID'];
}
else {
$MysqLi->query("INSERT INTO Tags (Tag) VALUES ('$tag')");
$tag_id = $MysqLi->insert_id;
}
$MysqLi->query("INSERT INTO TagMap (ArticleID,TagID) VALUES ($article_id,$tag_id)");
我想知道是否有更快的方法在 MysqL 中的一个查询中执行此操作。
此外,当我们有一个标签列表为时,我希望找到批处理 INSERT
(可能通过 LOAD DATA LOCAL INFILE
)的方法
ArticleID,Tag
1,tag2
2,tag11
4,tag3
解决方法
一种模式:
CREATE PROCEDURE load_to_TagMap ()
BEGIN
-- create table for loading data
CREATE TABLE tmp_TagMap ( ArticleID INT,Tag VARCHAR(255) ) ENGINE = Memory;
-- load data from file
LOAD DATA INFILE '/directory/filename.ext'
INTO TABLE tmp_TagMap
SKIP 1 LINES;
-- add absent tags into Tags table
INSERT INTO Tags (Tag)
SELECT tmp_TagMap.Tag
FROM tmp_TagMap
LEFT JOIN Tags USING (Tag)
WHERE Tags.Tag IS NULL;
-- insert loaded data into TagMap table with lookup
INSERT INTO TagMap
SELECT ArticleID,TagID
FROM Tag
JOIN tmp_TagMap USING (Tag);
-- remove loaded data table
DROP TABLE tmp_TagMap;
END
在 PHP 中只需执行 CALL load_to_TagMap;
。
过度规范化。
“标签”往往是短字符串,对吗?为每个创建一个 INT
并进行二次查找的开销是不值得的。将 Tags
和 TagMap
替换为
CREATE TABLE Tags
(
ArticleID int(11) unsigned NOT NULL,Tag VARCHAR(255) NOT NULL,PRIMARY KEY(ArticleID,Tag)
INDEX(Tag,ArticleID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE utf8_general_ci
这可能很有趣:http://mysql.rjweb.org/doc.php/lists
更多
SELECT COUNT(*) FROM Tags WHERE Tag = '...';
非常有效,即使 Tag 是 VARCHAR
。这也简化了您的代码——您不需要额外的代码来碰撞计数器;当文章被删除时,也很容易减少计数器:
DELETE FROM Tags WHERE ArticleID = ...;
如果您希望每个标签包含 10 万篇文章,那么可能存在性能问题。您需要多少文章和标签?
如果大图是“为 Tag='...显示'最新的 10 篇文章',那么性能问题将出现在 ORDER BY date DESC LIMIT 10
中。目前涉及到文章表的连接,请检查用于未“删除”、排序等。但我有一个解决方案:http://mysql.rjweb.org/doc.php/lists