似乎从CSV加载数据比使用Pandas的SQL(Postgre SQL)更快。 (我有一个SSD)
这是我的测试代码:
import pandas as pd
import numpy as np
start = time.time()
df = pd.read_csv('foo.csv')
df *= 3
duration = time.time() - start
print('{0}s'.format(duration))
engine = create_engine('postgresql://user:password@host:port/schema')
start = time.time()
df = pd.read_sql_query("select * from mytable", engine)
df *= 3
duration = time.time() - start
print('{0}s'.format(duration))
foo.csv和数据库是相同的(两列中的数据和列数相同,4列,100 000行充满随机int)。
CSV需要0.05秒
SQL需要0.5秒
您认为CSV比SQL快10倍是否正常?我想知道我是否在这里遗漏了一些东西......
答案 0 :(得分:8)
这是一种正常行为,读取csv文件始终是简单加载数据的最快方式之一
CSV非常简单易懂。直接从它加载将非常快。对于具有复杂结构的海量数据库,CSV不是一种选择。 SQL从表中选择数据是非常快速的,并将数据返回给您。当然,如果您可以选择,修改和操纵数据,则会增加您的通话费用。
假设您在csv中的1920年至2017年的csv中有时间序列,但您只需要从2010年到今天的数据。
csv方法将加载整个csv,然后选择2010年至2017年。
SQL方法将通过SQL select函数预先选择年份
在这种情况下,SQL会更快。
答案 1 :(得分:1)
使用PostgreSQL数据库时,您可以结合使用SQL和CSV来充分利用这两种方法。 SQL准确选择您需要的数据和CSV输出,以快速将其加载到pandas DataFrame中。
conn = psycopg2.connect(**conn_params)
with conn.cursor() as cur:
sql = 'SELECT * FROM large_table'
buf = io.StringIO()
cur.copy_expert(f'COPY ({sql}) TO STDOUT WITH CSV HEADER', buf)
buf.seek(0)
df = pd.read_csv(buf, header=0, low_memory=False,
true_values='t', false_values='f')
conn.close()
这使用PostgreSQL的快速COPY命令与psycopg2的copy_expert()
函数结合,将查询结果读入CSV格式的字符串缓冲区。然后,您可以在该字符串缓冲区上使用pandas read_csv()
。
缺点是您可能必须在之后转换数据类型(例如,时间戳将是字符串)。 read_csv()
函数有一些参数可以帮助解决这个问题(例如parse_dates
,true_values
,false_values
,...)。
在我的用例(3000万行,15列)中,与pandas read_sql()
函数相比,这使我的性能提升了大约2-3倍。
答案 2 :(得分:1)
虽然Steven G对此过程的解释基本上是您问题的答案,而Simon G的COPY
解决方案是我能找到的最有效的解决方案,我决定更深入地研究你的问题,并实际衡量与之相关的不同方面。
在https://git.mikael.io/mikaelhg/pandas-pg-csv-speed-poc,有一个项目包含各种替代解决方案的pytest基准。
此测试的CSV比问题大一个数量级,形状为(3742616, 6)
。只是为了确保各种缓冲区不太可能使结果偏斜。
感谢Finnish Traffic Safety Bureau Trafi's open data initiative提供测试数据。
对于PostgreSQL安装,它位于规范的Docker容器中,并以更高的shared_buffers
和work_mem
值启动,数据文件存储在主机' s /dev/shm
挂载点,以取消实际的磁盘I / O.它的UNIX套接字连接点也同样暴露出来。
version: '3'
services:
db:
image: 'postgres:10-alpine'
command: "postgres -c 'shared_buffers=512MB' -c 'temp_buffers=80MB' -c 'work_mem=256MB'"
ports:
- '5432:5432'
volumes:
- '/dev/shm/pgtest/data:/var/lib/postgresql/data'
- '/dev/shm/pgtest/run:/var/run/postgresql'
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
test:
image: pandas_speed_poc:temp
build:
context: .
dockerfile: Dockerfile.test-runner
volumes:
- '.:/app'
- '/dev/shm/pgtest/run:/var/run/postgresql'
working_dir: '/app'
user: '1000'
测试运行器是一个简单的Ubuntu 18.04容器:
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get -qq update && \
apt-get -y -qq install python3-dev python3-pip python3-psycopg2 \
build-essential \
bash less nano wait-for-it
RUN pip3 install sqlalchemy numpy pandas \
pytest pytest-benchmark
WORKDIR /app
CMD wait-for-it db:5432 -- /bin/bash -c "trap : TERM INT; sleep infinity & wait"
实际基准是为unittest
编写的Python 3 pytest-benchmark
:
#!/usr/bin/python3
from sqlalchemy import create_engine
import psycopg2
import psycopg2.extensions
import pandas as pd
import numpy as np
import io
import time
import gzip
import unittest
import pytest
DATA_FILE = 'data/licenses.csv.gz'
DROP_TABLE = "DROP TABLE IF EXISTS licenses"
CREATE_TABLE = """
CREATE TABLE licenses (
a VARCHAR(16),
b CHAR(3),
c CHAR(6),
d INTEGER,
e INTEGER,
f INTEGER
)
"""
COPY_FROM = """
COPY licenses (a, b, c, d, e, f) FROM STDIN
WITH (FORMAT CSV, DELIMITER ';', HEADER)
"""
COPY_TO = "COPY licenses TO STDOUT WITH (FORMAT CSV, HEADER)"
SELECT_FROM = 'SELECT * FROM licenses'
VACUUM = "VACUUM FULL ANALYZE"
DB_UNIX_SOCKET_URL = 'postgresql://test:test@/test'
DB_TCP_URL = 'postgresql://test:test@db/test'
def my_cursor_factory(*args, **kwargs):
cursor = psycopg2.extensions.cursor(*args, **kwargs)
cursor.itersize = 10240
return cursor
class TestImportDataSpeed(unittest.TestCase):
@pytest.fixture(autouse=True)
def setupBenchmark(self, benchmark):
self.benchmark = benchmark
@classmethod
def setUpClass(cls):
cls.engine = create_engine(DB_TCP_URL, connect_args={'cursor_factory': my_cursor_factory})
connection = cls.engine.connect().connection
cursor = connection.cursor()
cursor.execute(DROP_TABLE)
cursor.execute(CREATE_TABLE)
with gzip.open(DATA_FILE, 'rb') as f:
cursor.copy_expert(COPY_FROM, file=f, size=1048576)
connection.commit()
connection.set_session(autocommit=True)
cursor.execute(VACUUM)
cursor.close()
connection.close()
def test_pd_csv(self):
def result():
return pd.read_csv(DATA_FILE, delimiter=';', low_memory=False)
df = self.benchmark(result)
assert df.shape == (3742616, 6)
def test_psycopg2_cursor(self):
def result():
connection = self.engine.connect().connection
cursor = connection.cursor()
cursor.itersize = 102400
cursor.arraysize = 102400
cursor.execute(SELECT_FROM)
rows = cursor.fetchall()
cursor.close()
connection.close()
return pd.DataFrame(rows)
df = self.benchmark(result)
assert df.shape == (3742616, 6)
def test_pd_sqla_naive(self):
def result():
return pd.read_sql_query(SELECT_FROM, self.engine)
df = self.benchmark(result)
assert df.shape == (3742616, 6)
def test_pd_sqla_chunked(self):
def result():
gen = (x for x in pd.read_sql(SELECT_FROM, self.engine, chunksize=10240))
return pd.concat(gen, ignore_index=True)
df = self.benchmark(result)
assert df.shape == (3742616, 6)
def test_pg_copy(self):
connection = self.engine.connect().connection
cursor = connection.cursor()
def result(cursor):
f = io.StringIO()
cursor.copy_expert(COPY_TO, file=f, size=1048576)
f.seek(0)
return pd.read_csv(f, low_memory=False)
df = self.benchmark(result, cursor)
assert df.shape == (3742616, 6)
最终结果:
speed_test.py .....
-------------------------------------------------------------------------------- benchmark: 5 tests -------------------------------------------------------------------------------
Name (time in s) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_pd_csv 1.4623 (1.0) 1.4903 (1.0) 1.4776 (1.0) 0.0110 (1.21) 1.4786 (1.0) 0.0171 (1.15) 2;0 0.6768 (1.0) 5 1
test_pg_copy 3.0631 (2.09) 3.0842 (2.07) 3.0732 (2.08) 0.0091 (1.0) 3.0769 (2.08) 0.0149 (1.0) 2;0 0.3254 (0.48) 5 1
test_psycopg2_cursor 4.5325 (3.10) 4.5724 (3.07) 4.5531 (3.08) 0.0161 (1.77) 4.5481 (3.08) 0.0249 (1.68) 2;0 0.2196 (0.32) 5 1
test_pd_sqla_naive 6.0177 (4.12) 6.0523 (4.06) 6.0369 (4.09) 0.0147 (1.62) 6.0332 (4.08) 0.0242 (1.63) 2;0 0.1656 (0.24) 5 1
test_pd_sqla_chunked 6.0247 (4.12) 6.1454 (4.12) 6.0889 (4.12) 0.0442 (4.86) 6.0963 (4.12) 0.0524 (3.52) 2;0 0.1642 (0.24) 5 1
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
答案 3 :(得分:0)
CSV比SQL快得多是完全正常的,但它们不是同一个东西,即使你可以将它们用于同一个东西:
CSV用于顺序访问,即,您从文件的开头开始,逐个读取每一行,并根据需要对其进行处理。
SQL用于索引访问,即,您查看索引,然后转到您要查找的行。您还可以执行全表扫描,即不使用任何索引,这使得表基本上是一个膨胀的CSV。
您的查询是全表扫描,它不会查看索引,因为它适用于所有数据,所以是的,这是正常的。
另一方面,如果你尝试像
这样的查询select * from mytable where myindex = "myvalue";
与在csv中搜索相同的行相比,您将获得巨大的提升。这是因为SQL中的索引