关于python:如何提高INSERT语句的性能?

How can I improve my INSERT statement performance?

在这里,Josh的回答让我对如何将256x64x250值数组插入MySQL数据库有了一个很好的开端。当我在我的数据上尝试他的insert语句时,速度非常慢(比如对于16MB文件,只需6分钟)。

1
2
3
4
5
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():

1
2
3
4
5
6
7
8
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分钟,这对于这个数量的数据来说似乎太长了。

我的原始文件有两个版本:有头文件还是没有头文件。我很想尝试加载数据,但我不知道如何正确解析数据。


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

因此,如果可能,请生成一个csv文件,然后使用加载数据内嵌。

希望这有帮助:)

编辑

所以我取了你的一个原始数据文件rolloff.dat,写了一个快速而脏的程序,把它转换成下面的csv格式。

从以下位置下载frames.dat:http://rapidshare.com/files/454896698/frames.dat

帧.dat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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文件加载到该表中。这是一个临时表,允许您在加载到适当的生产/报告表之前操作/转换数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
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兼容-检查路径名并将'
'更改为'')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 '

'

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秒内加载。

希望这有帮助:)


如果数据是numpy数组,则可以尝试以下操作:

1
2
3
4
5
6
7
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)

1
2
3
4
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)

希望有帮助


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


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

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

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

从手册中:

更改表…禁用键告诉MySQL停止更新非唯一索引。更改表…然后应使用启用键重新创建缺少的索引。MySQL使用一种比逐个插入密钥快得多的特殊算法来实现这一点,因此在执行大容量插入操作之前禁用密钥会大大加快速度。正在使用alter table…除前面提到的权限外,禁用密钥还需要索引权限。


如果我理解正确,executeMany()将为要插入的每一行执行insert-into查询。可以通过创建一个包含所有值的插入查询来改进这一点,该查询应如下所示:

1
2
3
4
5
6
7
INSERT INTO data
  (frame, sensor_row, sensor_col, value)
VALUES
 (1, 1, 1, 1),
 (2, 2, 2, 2),
 (3, 3, 3, 3),
 ...

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


您可以使用列表理解而不是for循环:

1
2
3
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%。