如何提高INSERT语句的性能?

时间:2011-03-27 15:03:10

标签: python mysql

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,但我无法弄清楚如何正确解析数据。

6 个答案:

答案 0 :(得分:6)

插入400万行(16MB数据)的最快方法是使用负载数据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

该文件仅包含具有每行和col的值的帧的数据 - 因此排除零。从原始文件生成了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')< / p>

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

希望这会有所帮助:)

答案 1 :(得分: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)

希望有所帮助

答案 2 :(得分:1)

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

答案 3 :(得分:0)

如果我理解正确,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代码应该在括号中生成行值,并从中创建一个查询字符串,最后执行一次查询。

答案 4 :(得分:0)

在每个语句上插入多行是优化的一种方式。但是,为什么需要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权限。

答案 5 :(得分:-1)

您可以使用列表综合而不是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%。