0

最近我一直在尝试优化我的表,主要是因为我通过学校的一些课程学到了更多关于数据库设计的知识。我也选择这样做,因为我在某些查询上遇到了很多超时,最近发现这确实是我的数据库设计错误。

所以基本上,我将在这张表上执行 SELECT、UPDATE、INSERT 和 DELETE。

这是我当前的数据库架构:

-- ----------------------------
-- Table structure for `characters_items`
-- ----------------------------
DROP TABLE IF EXISTS `characters_items`;
CREATE TABLE `characters_items` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `master_id` int(10) unsigned NOT NULL DEFAULT '0',
  `item_id` smallint(6) NOT NULL,
  `amount` int(11) NOT NULL,
  `slot_id` smallint(9) NOT NULL DEFAULT '0',
  `type` tinyint(4) NOT NULL DEFAULT '0',
  `extra_data` text,
  PRIMARY KEY (`id`),
  KEY `master_id` (`master_id`),
  CONSTRAINT `characters_items_ibfk_1` FOREIGN KEY (`master_id`) REFERENCES `characters` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=904 DEFAULT CHARSET=latin1;

在我的程序中,我将处理大量数据(一次最多 500 行,如您所见,这是一个包含所有字符项的表)。

我还了解到,如果您正在进行数据操作,索引值会减慢您的查询速度。

以下是我将使用的一些查询:

            StringBuilder query = new StringBuilder();

            client.ClearParameters();
            client.AddParameter("master_id", this.owner.MasterId);
            client.AddParameter("type", (byte)CharacterItemType.Bank);
            client.AddParameter("capacity", this.Capacity);

            // Grab the original items.
            DataRow[] data = client.ReadDataTable("SELECT item_id,amount,slot_id FROM characters_items WHERE master_id=@master_id AND type=@type LIMIT @capacity").Select();
            Item[] originalItems = new Item[this.Capacity];
            if (data != null && data.Length > 0)
            {
                for (short i = 0; i < data.Length; i++)
                {
                    DataRow row = data[i];

                    short id = (short)row[0];
                    int count = (int)row[1];
                    short slotId = (short)row[2];

                    originalItems[slotId] = new Item(id, count);
                }
            }

            // Now we compare the items to see if anything has been changed.
            Item[] items = this.ToArray();
            for (short i = 0; i < items.Length; i++)
            {
                Item item = items[i];
                Item original = originalItems[i];

                // item was added.
                if (item != null && original == null)
                {
                    query.Append("INSERT INTO characters_items (master_id,item_id,amount,slot_id,type,extra_data) ");
                    query.Append("VALUES (");
                    query.Append(this.owner.MasterId);
                    query.Append(",");
                    query.Append(item.Id);
                    query.Append(",");
                    query.Append(item.Count);
                    query.Append(",");
                    query.Append(i);
                    query.Append(",");
                    query.Append((byte)CharacterItemType.Bank);

                    string extraData = item.SerializeExtraData();
                    if (extraData != null)
                    {
                        query.Append(",'");
                        query.Append(extraData);
                        query.Append("'");
                    }
                    else
                    {
                        query.Append(",null");
                    }

                    query.Append(");");
                }
                // item was deleted.
                else if (item == null && original != null)
                {
                    query.Append("DELETE FROM characters_items WHERE slot_id=");
                    query.Append(i);
                    query.Append(" AND master_id=");
                    query.Append(this.owner.MasterId);
                    query.Append(" AND type=");
                    query.Append((byte)CharacterItemType.Inventory);
                    query.Append(" LIMIT 1;");
                }
                // item was modified.
                else if (item != null && original != null)
                {
                    if (item.Id != original.Id || item.Count != original.Count)
                    {
                        query.Append("UPDATE characters_items SET item_id=");
                        query.Append(item.Id);
                        query.Append(",amount=");
                        query.Append(item.Count);

                        string extraData = item.SerializeExtraData();
                        if (extraData != null)
                        {
                            query.Append(",extra_data='");
                            query.Append(extraData);
                            query.Append("'");
                        }
                        else
                        {
                            query.Append(",extra_data=null");
                        }

                        query.Append(" WHERE master_id=@master_id AND type=@type AND slot_id=");
                        query.Append(i);
                        query.Append(";");
                    }
                }
            }

            // If a query was actually built, we will execute it.
            if (query.Length > 0)
            {
                client.SetConnectionTimeout(60);
                client.ExecuteUpdate(query.ToString());
                return true;
            }
        }
        catch (Exception ex)
        {
            Program.Logger.PrintException(ex);
        }
        return false;

如您所见,我几乎总是引用 slot_id、type 和 master_id 字段。我想知道如果我将 slot_id 和 type 字段设置为索引字段,它将如何影响我的整体数据操作性能?会受到正面的影响,还是负面的影响?

请给我一些建议(C#代码除外,我稍后会修复它!)

4

3 回答 3

3

首先,当您可以使用绑定参数时,切勿动态构造 SQL 文本。绑定参数可以保护您免受SQL 注入,并且可以通过允许 DBMS准备一次 SQL 语句并多次重用它来提高性能。

至于索引……它们通常是查找和修改数据之间的权衡——它们加快了前者的速度,减慢了后者的速度。但是,如果以一种也包含搜索2的方式修改数据,则索引实际上最终也可以加快修改速度。

索引应始终根据您的应用程序正在执行的实际查询量身定制,在您的情况下包括以下内容:

  • SELECT ... WHERE master_id=... AND type=...
  • INSERT ...
  • DELETE ... WHERE slot_id=... AND master_id=... AND type=...
  • UPDATE ... WHERE master_id=... AND type=... AND slot_id=...

所有 3 个 WHERE 子句都可以由{master_id, type, slot_id}. 只有 INSERT 语句(其本质上没有 WHERE)会受到这个附加索引的影响。

注意事项:

  • 如果复合索引的某些字段的选择性较低,您可能会考虑将它们从索引中删除,以使其更小且对缓存更友好。例如,如果master_id预计共享相同的行数会很低,则在其上建立索引master_id不会显着影响搜索性能,但会使索引更小,更容易/更快地维护。
  • 另一方面,您也可以考虑在索引中包含 WHERE 子句不直接使用但在查询中其他地方列出的字段。例如,为了覆盖SELECT item_id, amount, slot_id我们可以将item_idand添加amount到索引的“后沿”(slot_id已经在索引中)。
  • MySQL/InnoDB总是对表进行集群,因此二级索引的价格相对较高(参见本文“集群的缺点”

正如你所看到的,所有这些都是一个非常复杂的平衡行为,即使专家也不能总是预测最佳平衡。因此,如果有疑问,请在决定之前进行测量

对于索引主题和一般数据库性能的出色介绍,我强烈推荐:使用索引。卢克!


1假设它们被正确使用。

2通常:DELETE ... WHERE ...UPDATE ... WHERE ...

于 2012-06-20T01:32:05.890 回答
1

为了给定 UPDATE 和 DELETE 语句的最佳性能,我建议:

ALTER TABLE characters_items
ADD KEY characters_items_IX1 (master_id, item_id, slot_id);

为了 SELECT 语句以及 DML 语句的最佳性能,可以修改索引以包含两个附加列:

ALTER TABLE characters_items
ADD KEY characters_items_IX1 (master_id, item_id, slot_id, type, amount);

(注意:您只需添加其中一个索引,不需要两者。)


我们观察到您的 UPDATE 和 DELETE 语句在所有三列上都指定了等于谓词。在这种情况下,您可能希望索引中的列从最高基数到最低基数排序。(也就是说,具有最多不同值的列,首先是最高选择性,然后是其他列。)

对于表中的大量行,这样的索引很可能会提高您的 UPDATE 和 DELETE 操作的性能。

(您不可能看到任何性能差异,因为您的表上的 auto_increment 值只有 904,这意味着您的表中可能只有不到一千行。)

如果 master_id 已经“几乎唯一”,那么该列上的现有索引就足够了。

如果添加我推荐的索引,则现有索引是多余的,可以删除。(任何使用现有索引的查询都可能使用新索引,以 master_id 作为前导列。)

是的,索引设计需要权衡取舍。执行 DML 操作时,还需要执行额外的工作来维护索引。

如果它们不是选择性的,或者如果您没有任何可以使用它们的查询,您不会只想在 slot_id 或 item_id 上添加索引。没有使用的索引是没有意义的。

至于其他索引,这实际上取决于您正在执行的其他语句,特别是 SELECT 语句。我们真的很想看看谓词(WHERE 子句和 JOIN 条件),看看额外的索引是否有帮助。


附录:

问:单独添加密钥和成组添加密钥有什么区别?(就像你给出的例子)

在这种情况下,三个单独的索引(在 master_id、item_id 和 slot_id 上)将没有用,因为执行计划很可能只使用其中一个,理想情况下,具有最高选择性的索引。“组合索引”的执行计划可能比全表扫描更快,但它们很少能胜过已经包含所有列的单个索引。

最大的区别是索引中的“前导”列。如果您在索引的前导列上没有谓词(WHERE 子句)或 ORDER BY,则不太可能使用该索引。


SELECT 语句的最佳索引是“覆盖”索引,即包含查询中引用的所有列的索引,这样可以从索引中满足查询,而无需引用数据中的页面桌子。

ADD KEY characters_items_IX1 (master_id, item_id, slot_id, type, amount);
于 2012-06-19T23:19:31.713 回答
0

如果索引对 select 语句的 where 子句具有高选择性,则它们将加速您的选择。

检查您最常运行哪些选择语句,然后对您在 where 子句中使用的字段编制索引。如果您使用通配符(尤其是像 '%something%' 这样的东西),索引将不太有用。

我不记得 MySQL 是否可以为索引包含列,但如果是这样,您可能会从将 select 语句中但不在 where 子句中的列添加为索引中的包含列中获得额外的好处。

否则,将执行的实际操作是索引查找,然后是键查找。索引始终将关联数据行的主键作为包含列,因此一旦找到索引键,就会通过其主键查找该行。如果可以避免键查找,则可以显着降低查询的 IO 成本。当然,这会略微增加插入和更新的成本,并且会大大增加数据库占用的空间。

于 2012-06-19T23:17:34.240 回答