MySQL缓慢,表格中包含大文本字段

时间:2016-07-11 13:52:17

标签: mysql performance

我们在MySQL(以及MariaDB)中遇到了一个奇怪的问题。一个带有2个表(InnoDB引擎)的简单数据库,两个表都包含(或其他几个)3或4个文本列,大约有XML数据。 1-5kB大小。 每个表有大约40000行,除了外键之外没有索引。

奇怪的部分是运行select语句。 XML列不会在select语句(select,where,order,group,...)中的任何位置使用,但它们会降低执行速度。如果这些列为null,则select语句在不到2秒的时间内执行,但如果它们包含数据,则执行时间会跳转到大约20秒。那是为什么?!

这是一个生成如上所述行为的示例的脚本:

CREATE TABLE tableA (
    id                  bigint(20)      NOT NULL AUTO_INCREMENT,
    col1                bigint(20)      NULL,
    col2                bigint(20)      NULL,
    date1               datetime        NULL,
    largeString1        text            NULL,
    largeString2        text            NULL,
    largeString3        text            NULL,
    largeString4        text            NULL,
    PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;

CREATE TABLE tableB (
    id              bigint(20)  NOT NULL AUTO_INCREMENT,
    col1            bigint(20)  NULL,
    col2            varchar(45) NULL,
    largeString1    text        NULL,
    largeString2    datetime    NULL,
    largeString3    text        NULL,
    PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;

fillTables:

DELIMITER ;;
CREATE PROCEDURE `fillTables`(
    numRows INT
)
BEGIN

    DECLARE i INT;
    DECLARE j INT;
    DECLARE largeString TEXT;
    SET i = 1;

    START TRANSACTION;

    WHILE i < numRows DO
        SET j = 1;
        SET largeString = '';
        WHILE j <= 100 DO
            SET largeString = CONCAT(largeString, (SELECT UUID()));
            SET j = j + 1;
        END WHILE;

        INSERT INTO tableA (id, col1, col2, date1, largeString1,
                           largeString2, largeString3, largeString4)
           VALUES (i, FLOOR(1 + RAND() * 2), numRows - i, 
                   date_sub(now(), INTERVAL i hour),
                   largeString, largeString, largeString, largeString);
        INSERT INTO tableB (id, col1, col2, largeString1,
                           largeString2, largeString3)
           VALUES (numRows - i, i, (SELECT UUID()),
                   largeString, largeString, largeString);
        SET i = i + 1;
    END WHILE;

    COMMIT;

    ALTER TABLE tableA ADD FOREIGN KEY (col2) REFERENCES tableB(id);
    CREATE INDEX idx_FK_tableA_tableB ON tableA(col2);
    ALTER TABLE tableB ADD FOREIGN KEY (col1) REFERENCES tableA(id);
    CREATE INDEX idx_FK_tableB_tableA ON tableB(col1);

END ;;

测试

CREATE PROCEDURE `test`(
    _param1     bigint
    ,_dateFrom  datetime
    ,_dateTo    datetime
)
BEGIN

    SELECT 
        a.id
        ,DATE(a.date1) as date
        ,COALESCE(b2.col2, '') as guid
        ,COUNT(*) as count
    FROM
        tableA a
        LEFT JOIN tableB b1 ON b1.col1 = a.id
        LEFT JOIN tableB b2 ON b2.id = a.col2
    WHERE
        a.col1 = _param1
        AND (_dateFrom IS NULL OR DATE(a.date1) BETWEEN DATE(_dateFrom) AND DATE(_dateTo))
    GROUP BY
        a.id
        ,DATE(a.date1)
        ,b2.col2
    ;

END;;
DELIMITER ;

使用随机数据填充表格

call fillTables(40000);

用于检索数据的存储过程:

call test(2, null, null);

此外,MSSQL在几分之一秒内执行select语句,没有任何表优化(即使没有定义外键)。

更新:

为两个表显示CREATE TABLE:

'CREATE TABLE `tableA` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `col1` bigint(20) DEFAULT NULL,
  `col2` bigint(20) DEFAULT NULL,
  `date1` datetime DEFAULT NULL,
  `largeString1` text,
  `largeString2` text,
  `largeString3` text,
  `largeString4` text,
  PRIMARY KEY (`id`),
  KEY `idx_FK_tableA_tableB` (`col2`),
  CONSTRAINT `tableA_ibfk_1` FOREIGN KEY (`col2`) REFERENCES `tableB` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40000 DEFAULT CHARSET=utf8'


'CREATE TABLE `tableB` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `col1` bigint(20) DEFAULT NULL,
  `col2` varchar(45) DEFAULT NULL,
  `largeString1` text,
  `largeString2` datetime DEFAULT NULL,
  `largeString3` text,
  PRIMARY KEY (`id`),
  KEY `idx_FK_tableB_tableA` (`col1`),
  CONSTRAINT `tableB_ibfk_1` FOREIGN KEY (`col1`) REFERENCES `tableA` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40000 DEFAULT CHARSET=utf8'

1 个答案:

答案 0 :(得分:4)

两个表都需要INDEX(col1)。没有它,这些需要表扫描:

WHERE a.col1 = _param1

ON b1.col1 = a.id

对于a,这将是'覆盖',因此更快:

INDEX(col1, date1, id, col2)

除非您需要,否则请勿使用LEFT

尽量不要在函数中隐藏列;它阻止为它们使用索引:

DATE(a.date1) BETWEEN ...

这可能对此有用:

    a.date1 >= DATE(_dateFrom)
AND a.date1  < DATE(_dateTo) + INTERVAL 1 DAY

至于20s vs 2s之谜 - 你有两次进行每次计时测试吗?第一次经常陷入I / O困境;第二个是记忆限制。

<强> ROW_FORMAT

在InnoDB中有4 ROW_FORMATs;他们处理大字符串(TEXTBLOB等)的方式大不相同。您提到查询使用NULL字符串比使用非空字符串运行得更快。使用默认的ROW_FORMAT,部分或全部XML字符串与其余列一起存储。经过一些限制后,其余部分被放入另一个区块。

如果一个大字段为NULL,则几乎不需要空格。

使用ROW_FORMAT=DYNAMIC(请参阅CREATE TABLEALTER TABLE),非空列将倾向于推送到其他块,而不是使记录的主要部分变得笨重。

这具有允许更多行适合单个块(溢出除外)的效果。反过来,这允许某些查询运行得更快,因为它们可以用更少的I / O获得更多信息。

阅读文档,我认为你需要这些:

SET GLOBAL innodb_file_format=Barracuda;
SET GLOBAL innodb_file_per_table=1;
ALTER TABLE tbl ROW_FORMAT=DYNAMIC;

在阅读文档时,您将遇到COMPRESSED。虽然这会使XML缩小3:1,但还有其他问题。我不知道它是否会变得更好。

缓冲池

innodb_buffer_pool_size应该是可用 RAM的70%左右。

相关问题