【问题标题】:How can I improve my INSERT statement performance?如何提高我的 INSERT 语句性能?
【发布时间】:2011-07-23 22:21:28
【问题描述】:

While Josh's answer here 让我在如何将 256x64x250 值数组插入 MySQL 数据库方面有了良好的开端。当我在我的数据上实际尝试他的 INSERT 语句时,结果发现速度非常慢(对于一个 16Mb 的文件来说需要 6 分钟)。

ny, nx, nz = np.shape(data)
query = """INSERT INTO `data` (frame, sensor_row, sensor_col, value) VALUES (%s, %s, %s, %s)"""
for frames in range(nz):
    for rows in range(ny):
        for cols in range(nx):
            cursor.execute(query, (frames, rows, cols, data[rows,cols,frames]))

我正在阅读MySQL for Python,它解释说这不是正确的方法,因为执行 400 万个单独的插入非常低效。

现在我的数据包含很多零(实际上超过 90%),所以我输入了一个 IF 语句,所以我只插入大于零的值,我使用了 executemany()

query = """INSERT INTO `data` (frame, sensor_row, sensor_col, value) VALUES (%s, %s, %s, %s ) """
values = []
for frames in range(nz):
    for rows in range(ny):
        for cols in range(nx):
            if data[rows,cols,frames] > 0.0:
                values.append((frames, rows, cols, data[rows,cols,frames]))           
cur.executemany(query, values)

这奇迹般地将我的处理时间缩短到 20 秒左右,其中 14 秒用于创建 列表(37k 行),4 秒用于实际插入数据库。

所以现在我想知道,我怎样才能进一步加快这个过程?因为我觉得我的循环效率非常低,必须有更好的方法。如果我需要为每只狗插入 30 个测量值,这仍然需要 10 分钟,这对于这么多数据来说似乎太长了。

这是我的原始文件的两个版本:with headerswithout headers。我很想试试 LOAD DATA INFILE,但我不知道如何正确解析数据。

【问题讨论】:

  • 数据从何而来?
  • 这是一个大文本文件,每个“框架”上方都有一些标题,您具体想知道什么?
  • 我只是好奇这个耗时的过程是读取文件还是嵌套的 for 循环来创建查询。
  • 什么是data?也许你可以通过在data上应用一些函数来获得values?这可以大大加快速度。
  • @alp & @pajton: data 是一个 numpy 数组,它加载在内存中,所以我猜它来自嵌套循环。如果有另一种方法来检索所有非零值及其索引,那么我会全力以赴

标签: python mysql


【解决方案1】:

我不使用 Python 或 mySQL,但批量插入性能通常可以通过事务来提高。

【讨论】:

  • 我正在努力思考如何将事务添加到组合中来提高性能...
  • 为什么我应该投反对票,因为你碰巧思考困难?
  • @Tim - 我很确定否决票不是 Marc 的。 (也不是我。)我可能是错的,但我猜是 ceejayoz,他是这个线程中唯一一个今天注册任何反对票的参与者。我讨厌匿名投票;选民至少应该给发帖人一些关于哪里出了问题的反馈。
  • 我没有对你投反对票,但与其他答案相比,我也看不出它对我有什么帮助。如果只是因为我对数据库知之甚少,以至于我什至不知道您在这种情况下所说的“事务”是什么意思
  • @Tim - 好吧,我不确定 30 个数据点在那里会有很大的不同,但内容很好。您应该将其移至答案中。在这里 +1(数学应该告诉你 -1 不是我)
【解决方案2】:

如果我理解正确,executemany() 会为您要插入的每一行执行一个 INSERT INTO 查询。这可以通过创建一个包含所有值的单个 INSERT 查询来改进,应该如下所示:

INSERT INTO data
  (frame, sensor_row, sensor_col, value)
VALUES
 (1, 1, 1, 1),
 (2, 2, 2, 2),
 (3, 3, 3, 3),
 ...

您的 python 代码应该生成括号中的行值并从中创建一个查询字符串以最终执行一次查询。

【讨论】:

  • 这就是这条线的作用@Alp:values.append((frames, rows, cols, data[rows,cols,frames])) 问题是,编写这样一个列表非常慢,我不知道如何优化它
  • 也许我没明白,但是看到你的代码,我认为会执行以下查询:INSERT INTO data (frame, sensor_row, sensor_col, value) VALUES (1, 1, 1 , 1);, INSERT INTO data (frame, sensor_row, sensor_col, value) VALUES (2, 2, 2, 2); 等等。如果我错了,请纠正我。
  • print(query, values) 返回('INSERT INTO data` (frame, sensor_row, sensor_col, value) VALUES (%s, %s, %s, %s ) ', [(0, 31 , 45, 0.40000001), (0, 31, 46, 0.40000001), (0, 32, 45, 0.40000001),`
  • 好吧,认为我的回答没用。
【解决方案3】:

您可以使用列表理解来代替 for 循环:

values = [(frames, rows, cols, data[rows,cols,frames]) \
        for frames in range(nz) for rows in range(ny) \
        for cols in range(nx) if data[rows,cols,frames] > 0.0]           

我估计这可以让您稍微加快速度,例如 10-20%。

【讨论】:

  • 从尝试来看,它似乎并没有改善这个过程。我会再试几次。
  • 我已经尝试了几次,我的循环或列表理解都需要将近 14 秒。 executemany 部分“仅”4 秒
  • 我知道,我其实是希望这能给它一些推动作用:\
【解决方案4】:

在每个语句中插入多行是一种优化方法。但是,为什么需要 3 个循环?也许某种数据转换可能会有用。

另一个选项是在插入期间禁用索引,如果您确定不会有任何重复数据(假设您实际上在表上有索引)。必须为每条语句更新索引,并检查以防止重复。

在开始插入之前调用 ALTER TABLE tablename DISABLE KEYS,完成后调用 ALTER TABLE tablename ENABLE KEYS 看看是否有帮助

来自手册:

ALTER TABLE ... DISABLE KEYS 告诉 MySQL 停止更新非唯一索引。 ALTER TABLE ... ENABLE KEYS 然后应该用于重新创建丢失的索引。 MySQL 使用一种比逐个插入键快得多的特殊算法来执行此操作,因此在执行批量插入操作之前禁用键应该会大大加快速度。使用 ALTER TABLE ... DISABLE KEYS 除了前面提到的权限外,还需要 INDEX 权限。

【讨论】:

  • 我想我做错了什么,因为我尝试了 cur.execute("""ALTER TABLE data ENABLE KEYS""") 并得到 'data' 的表存储引擎没有这个选项所以我想我'我做错了什么。
  • 嗯,也许它只适用于 MyISAM 表。你在用什么?
  • 我在 InnoDB 上安装了它(HeidiSQL 中的默认设置),但将其更改为 MyISAM 会出现相同的错误。我猜我的表格设计与数据格式不匹配
  • 是的,它不适用于 innoDB,但对于 MyISAM 应该没问题,这很奇怪。
  • 但是,InnoDB 对于非并发操作来说非常慢,在这种情况下使用多个线程/连接插入数据可能会更快,但如果 MyISAM 是一个选项,您可能想尝试一下。禁用密钥应该适用于 MyISAM。
【解决方案5】:

如果数据是一个numpy数组,你可以试试这个:

query = """INSERT INTO `data` (frame, sensor_row, sensor_col, value) VALUES (%s, %s, %s, %s ) """
values = []
rows, cols, frames = numpy.nonzero(data)
for row, col, frame in zip(rows, cols, frames):
    values.append((frame, row, col, data[row,col,frame]))

cur.executemany(query, values)

query = """INSERT INTO `data` (frame, sensor_row, sensor_col, value) VALUES (%s, %s, %s, %s ) """
rows, cols, frames = numpy.nonzero(data)
values = [(row, col, frame, val) for row, col, frame, val in zip(rows, cols, frames, data[rows,cols,frames])]
cur.executemany(query, values)

希望对你有帮助

【讨论】:

  • 我认为列表理解有问题,但你的第一个循环非常快:0.34 秒
  • 您遇到什么错误?你是否也在运行 rows、cols、frames = numpy.nonzero(data)?我已经编辑了帖子以使其清楚?
  • 不解释了 ;-) 它甚至更快!
  • 只是为了好奇,你能试试吗(注意 ;,我不能在 cmets 中添加新行):ndx = numpy.nonzero(data);值 = numpy.hstack((numpy.transpose(ndx), data[ndx].reshape((-1, 1)))); cur.executemany(查询,值)或 ndx = numpy.nonzero(数据);值 = numpy.hstack((numpy.transpose(ndx), data[ndx].reshape((-1, 1)))); cur.executemany(query, values.tolist())
  • 它需要 values.tolist(),但这在 3.65 秒内完成了整个事情。大部分时间都花在了executemany上。虽然当然它的可读性要低得多
【解决方案6】:

插入 400 万行(16MB 数据)的最快方法是使用 load data infile - http://dev.mysql.com/doc/refman/5.0/en/load-data.html

所以如果可能的话,生成一个 csv 文件,然后使用 load data infile..

希望这会有所帮助:)

编辑

所以我拿了你的一个原始数据文件 rolloff.dat 并编写了一个快速而肮脏的程序将其转换为以下 csv 格式。

从这里下载 frames.dat:http://rapidshare.com/files/454896698/frames.dat

Frames.dat

patient_name, sample_date dd/mm/yyyy, frame_time (ms), frame 0..248, row 0..255, col 0..62, value
"Krulle (opnieuw) Krupp",04/03/2010,0.00,0,5,39,0.4
"Krulle (opnieuw) Krupp",04/03/2010,0.00,0,5,40,0.4
...
"Krulle (opnieuw) Krupp",04/03/2010,0.00,0,10,42,0.4
"Krulle (opnieuw) Krupp",04/03/2010,0.00,0,10,43,0.4
"Krulle (opnieuw) Krupp",04/03/2010,7.94,1,4,40,0.4
"Krulle (opnieuw) Krupp",04/03/2010,7.94,1,5,39,0.4
"Krulle (opnieuw) Krupp",04/03/2010,7.94,1,5,40,0.7
"Krulle (opnieuw) Krupp",04/03/2010,7.94,1,6,44,0.7
"Krulle (opnieuw) Krupp",04/03/2010,7.94,1,6,45,0.4
...
"Krulle (opnieuw) Krupp",04/03/2010,1968.25,248,241,10,0.4
"Krulle (opnieuw) Krupp",04/03/2010,1968.25,248,241,11,0.4
"Krulle (opnieuw) Krupp",04/03/2010,1968.25,248,241,12,1.1
"Krulle (opnieuw) Krupp",04/03/2010,1968.25,248,241,13,1.4
"Krulle (opnieuw) Krupp",04/03/2010,1968.25,248,241,14,0.4

该文件仅包含具有每行和每列值的帧的数据 - 因此不包括零。从您的原始文件生成了 24799 行数据。

接下来,我创建了一个临时加载(暂存)表,frames.dat 文件将加载到该表中。这是一个临时表,可让您在加载到正确的生产/报告表之前操作/转换数据。

drop table if exists sample_temp;
create table sample_temp
(
patient_name varchar(255) not null,
sample_date date,
frame_time decimal(6,2) not null default 0,
frame_id tinyint unsigned not null,
row_id tinyint unsigned not null,
col_id tinyint unsigned not null,
value decimal(4,1) not null default 0,
primary key (frame_id, row_id, col_id)
)
engine=innodb;

剩下的就是加载数据(注意:我使用的是 Windows,因此您必须编辑此脚本以使其与 linux 兼容 - 检查路径名并将 '\r\n' 更改为 '\n')

truncate table sample_temp;

start transaction;

load data infile 'c:\\import\\frames.dat' 
into table sample_temp
fields terminated by ',' optionally enclosed by '"'
lines terminated by '\r\n'
ignore 1 lines
(
patient_name,
@sample_date,
frame_time,
frame_id,
row_id,
col_id,
value
)
set 
sample_date = str_to_date(@sample_date,'%d/%m/%Y');

commit;

Query OK, 24799 rows affected (1.87 sec)
Records: 24799  Deleted: 0  Skipped: 0  Warnings: 0

24K 行在 1.87 秒内加载完毕。

希望这会有所帮助:)

【讨论】:

  • MySQL 允许你设置轮廓符是什么。
  • @ceejayoz,如何确定我需要分配哪些列才能正确读取文件?或者我是否必须解析“原始”文件 Python 以删除标题等?
  • 我阅读了该页面,但我终其一生都无法弄清楚如何正确解析数据。注意详细说明,因为它会删除一些“冗余”步骤
  • 我很乐意为您提供帮助,但您能否发布更多关于加载数据后如何处理的信息,因为这将决定我的设计方向。发布您拥有的任何表格定义、患者人数、扫描/测量频率、典型查询等......
  • @f00,抱歉花了这么长时间,但是I decided to wrap the additional info up in a blog post.。如果您需要任何具体信息,请告诉我。
猜你喜欢
  • 2017-02-10
  • 2012-12-12
  • 2017-04-25
  • 2018-12-12
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多