【问题标题】:Simple SQL update query is slow on small table小表上的简单 SQL 更新查询很慢
【发布时间】:2016-05-13 04:08:22
【问题描述】:

我最近为我的 PHP 会话迁移到数据库存储。在创建会话处理函数后,我意识到我的写入速度很慢。

以下查询在 MySQL 上需要 0.04 秒,在 SQLite 上需要 0.08 秒。这似乎并不慢,直到我意识到新设置的表格只有 6 行。

UPDATE sm_sessions
    SET last_access =  '1463104877'
    WHERE session_id = 'smsess-5734112c09619927459593e9866792515f74ba3115d3a1093b3a31018'
    LIMIT 1

还有,这是表结构。

CREATE TABLE IF NOT EXISTS `sm_sessions` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `session_id` char(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `session_data` text COLLATE utf8mb4_unicode_ci,
  `last_access` int(10) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `session_id` (`session_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci AUTO_INCREMENT=57 ;

请注意,我已经尝试了所有方法,包括以下内容:

  • 添加了主键和自增“id”(这个字段一开始是不存在的,尝试添加看看会不会影响性能)
  • 在 session_id 上尝试了主键、唯一索引和常规索引
  • 尝试删除所有索引
  • 尝试修改查询以使用 where 子句中的 id 而不是 64 个字符的 session_id 进行更新(没有改进)
  • 尝试使用 REPLACE INTO 和 ON DUPLICATE KEY 更新(更糟)
  • 直接从 mysql cli 和 phpmyadmin(以及 sqlite cli 和 sqliteadmin)运行查询 - 性能相同,所以我知道它不是 php 或 pdo 减慢它的速度

我知道 0.04 秒还不错。但是,读取在 0.0006 秒左右完成。如果我有 10 次写入,那就是 0.4 秒。而且,是的,我知道读取通常总是比写入快,但是对于一个有 6 行、新设置和如此简单查询的表来说,0.04 或更差的速度听起来非常糟糕。

我觉得这个更新查询在 WORST 应该需要 0.01 秒,通常在 0.005 秒的范围内。这是一个合理的期望吗?

这是在本地完成的,但似乎即使在不同的服务器上写入也会相对较慢,因为其他一切都很快。我可以发布代码或 mysql/sqlite 设置,但我觉得这在最近制作的 6 行表上并不重要。

怎么了?

编辑|更新安装mysql时创建的mysql表,一个简单的更新查询耗时0.005秒。相同的数据库服务器。出了点问题。

EDIT2|将存储引擎从 InnoDB 更改为 MyISAM 使查询运行得更快 - 0.0009 秒。我会将此标记为已解决,我可能会,但有谁知道为什么会加快它的速度?

EDIT3|抱歉所有的编辑,但任何阅读的人都应该知道我在下面的答案中找到了解决方案。

【问题讨论】:

  • 是 0.04 秒还是 0.04 毫秒?无论哪种方式,除非您遇到严重的性能问题,否则这听起来不像是世界末日。
  • 0.04 秒(40 毫秒)。我觉得这是世界末日,因为在使用数据库会话管理时,每次会话写入都需要 0.04 秒。另外,我正在编写一个开源软件,如果没有这个,页面加载时间不到 0.01 秒。还有,不管是不是世界末日,我觉得还是不应该发生的。我在上面添加了一个编辑。
  • 如果您对这些类型的数据库调用感到害怕,那么您已经遇到了麻烦。如果要存储少量数据,可以使用 Redis 之类的东西,它通常比 MySQL 快,但限制更多。
  • MyISAM 不是事务性的,它非常脆弱。在轻负载下它会表现得更好,但重负载会受到表级写锁的严重影响。您确实需要在压力下进行基准测试,否则您的数字毫无意义。
  • 感谢基准测试,我能够找出问题在于客户端/服务器字符集和排序规则与数据库/表/字段字符集和排序规则不同。更改客户端/服务器字符集和排序规则以匹配数据库导致速度提高了约 40 倍。

标签: mysql sql performance sqlite


【解决方案1】:

感谢@tadman 的建议,我开始使用 mysqlslab 进行基准测试,并且我已经解决了问题。

我的连接和服务器字符集和排序规则设置为 latin1 和 utf8 的混合。

此表使用 utf8mb4 和 utf8mb4_general_ci 比较字符串。

更改连接和服务器以匹配后,我获得了

显示我的测试..

我发现问题之前,我运行了以下命令:

scott@scottsdevbox:/etc/mysql$ /usr/bin/mysqlslap --user=root --password=mypasswordhere --delimiter=";" --create="CREATE TABLE a (session_id char(64) COLLATE utf8mb4_unicode_ci NOT NULL, session_data text COLLATE utf8mb4_unicode_ci, last_access int(10) NOT NULL, PRIMARY KEY (session_id)) DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO  a (session_id ,session_data ,last_access)VALUES ('smsess-57356c97a576f244771728b1cf2b22c3d05ee2a891ab34c4c60cd5fe9',  'abczyx123string',  '1463120872');" --query="UPDATE a SET last_access =  '1463120866' WHERE session_id = 'smsess-57356c97a576f244771728b1cf2b22c3d05ee2a891ab34c4c60cd5fe9' LIMIT 1" --concurrency=1 --iterations=100 --engine="InnoDB"

并以 0.04 的速度得到通常的结果:

Benchmark
    Running for engine InnoDB
    Average number of seconds to run all queries: 0.046 seconds
    Minimum number of seconds to run all queries: 0.033 seconds
    Maximum number of seconds to run all queries: 0.211 seconds
    Number of clients running queries: 1
    Average number of queries per client: 1

然后,在更改我的服务器和客户端字符集(到 utf8mb4)和排序规则(到 utf8mb4_general_ci)以匹配数据库和字段之后..

命令:

scott@scottsdevbox:/etc/mysql$ /usr/bin/mysqlslap --user=root --password=mypasswordhere --delimiter=";" --create="CREATE TABLE a (session_id char(64) character set utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, session_data text character set utf8mb4 COLLATE utf8mb4_general_ci, last_access int(10) NOT NULL, PRIMARY KEY (session_id))  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; INSERT INTO  a (session_id ,session_data ,last_access)VALUES ('smsess-57356c97a576f244771728b1cf2b22c3d05ee2a891ab34c4c60cd5fe9',  'abczyx123string',  '1463120872');" --query="UPDATE a SET last_access =  '1463120866' WHERE session_id = 'smsess-57356c97a576f244771728b1cf2b22c3d05ee2a891ab34c4c60cdsed5fe9' LIMIT 1" --concurrency=1 --iterations=100 --engine="InnoDB"

结果:

Benchmark
    Running for engine InnoDB
    Average number of seconds to run all queries: 0.000 seconds
    Minimum number of seconds to run all queries: 0.000 seconds
    Maximum number of seconds to run all queries: 0.001 seconds
    Number of clients running queries: 1
    Average number of queries per client: 1

总之,我了解到更改您的客户端和服务器字符集和排序规则以匹配数据库、表和字段字符集和排序规则会提高查询速度

【讨论】:

    【解决方案2】:

    对数据库的每次更改都涉及锁定和同步开销,每个事务仅发生一次,并且不依赖于访问或写入的数据量。

    所以当DB的大小增加时,时间很可能不会变大。

    要使多次写入更快,请将它们全部放入同一个事务中。

    【讨论】:

      【解决方案3】:

      char(64) COLLATE utf8mb4_unicode_ci 不适合 ascii 字符串。

      • CHAR 是固定宽度,浪费空间。特别是,无论内容如何,​​它都占用 4*64 (=256) 字节。

      • COLLATE utf8mb4_unicode_ci 如果文本始终是字母、破折号和数字,则会增加不必要的复杂性。

      切换到VARCHAR(64) CHARACTER SET ascii。如果合适,session_data 可以保留 utf8mb4。

      【讨论】:

      • 谢谢,@Rick James,我一直想知道.. 对于我知道的任何值都是 ascii,就像上面一样,最好设置为 ascii,对吗?由于该字段将始终为 a-z0-9,因此即使整个应用程序已国际化,该字段也不需要 utf8,对吗?
      • 正确无误。我看到smsess-5734112c09619927459593e9866792515f74ba3115d3a1093b3a31018 正好是 64 个字节。 VARCHAR(64) CHARACTER SET ascii 中需要 1+64 个字节(长度为 1)。如果您知道您的所有字符串都是 64 个字符,那么 CHAR 将节省 1 个字节。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-05
      • 2021-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多