具有百万行的数据库表
Posted
技术标签:
【中文标题】具有百万行的数据库表【英文标题】:Database table with million of rows 【发布时间】:2018-12-21 07:48:37 【问题描述】:例如,我有一些 GPS 设备每秒钟向我的数据库发送信息
所以 1 台设备在 mysql 数据库中使用这些列创建 1 行 (8)
id=12341 日期=22.02.2018 时间=22:40 langitude=22.236558789 longitude=78.9654582 deviceID=24 name=device-name someinfo =asdadadasd
所以 1 分钟创建 60 行,24 小时创建 864000 行 1个月(31天)2678400 ROWS
所以 1 台设备每月在我的 db 表中创建 260 万行(每个月都会删除记录。) 所以如果有更多的设备将是 260 万 * 设备数
所以我的问题是这样的:
问题 1:如果我从 php 进行这样的搜索(仅适用于当天和 1 台设备)
SELECT * FROM TABLE WHERE date='22.02.2018' AND deviceID= '24'
最大可能结果为 86400 行 会不会让我的服务器过载太多
问题 2: 5 小时(18000 行)的限制对于数据库来说会是个问题,还是会像第一个示例一样加载服务器或更少
SELECT * FROM TABLE WHERE date='22.02.2018' AND deviceID= '24' LIMIT 18000
问题 3: 如果我只显示 1 个来自 db 的结果,是否会导致服务器过载
SELECT * FROM TABLE WHERE date='22.02.2018' AND deviceID= '24' LIMIT 1
这是否意味着如果我有数百万行,如果我只显示 1 个结果,那么 1000 行将加载服务器相同
【问题讨论】:
嗨,欢迎来到 Stack Overflow。如果我们知道您对这些查询做了什么,这将有助于回答您的问题。对于您的第一个查询,我怀疑您是否想查看一天中的所有 864,000 秒。我想你会用 PHP 做一些处理。您可以在 MySQL 中进行该处理,这通常会更有效。 btw 864000 秒是 10 天;p 300 万行不算多,还有 3000 万行。这取决于您使用它们的目的。您是每天一次、每小时一次、每分钟一次查询 100k 行吗?请解释您的用例。 【参考方案1】:数百万行不是问题,这就是 SQL 数据库旨在处理的问题,如果您拥有设计良好的架构和良好的索引。
使用正确的类型
不要将日期和时间存储为单独的字符串,而是将它们存储为单个 datetime
或单独的 date
和 time
类型。有关使用哪一个的更多信息,请参阅下面的索引。这既更紧凑,允许索引,更快的排序,而且无需进行转换即可使用date and time functions。
同样,请务必使用适当的numeric type 作为纬度和经度。您可能需要使用numeric
来确保精度。
由于您要存储数十亿行,因此请务必使用 bigint
作为主键。一个普通的 int 最多只能达到 20 亿。
将重复的数据移到另一个表中。
不要在每一行中存储有关设备的信息,而是将其存储在单独的表中。然后只将设备的 ID 存储在您的日志中。这将减少您的存储大小,并消除由于数据重复而导致的错误。请务必将设备 ID 声明为外键,这将提供 referential integrity 和索引。
添加索引
Indexes 允许数据库非常、非常高效地搜索数百万或数十亿行。确保您经常使用的行上有索引,例如您的时间戳。
date
和 deviceID
上缺少索引可能是您的查询如此缓慢的原因。如果没有索引,MySQL 必须查看数据库中称为full table scan 的每一行。这就是为什么您的查询如此缓慢,缺少索引的原因。
您可以通过explain
发现您的查询是否使用索引。
datetime
或 time
+ date
?
通常最好将您的日期和时间存储在单个列中,通常称为created_at
。然后你可以使用date
来获取日期部分,就像这样。
select *
from gps_logs
where date(created_at) = '2018-07-14'
有问题。问题是索引如何工作......或不工作。由于函数调用,where date(created_at) = '2018-07-14'
不会使用索引。 MySQL 将在每一行上运行date(created_at)
。这意味着性能会扼杀全表扫描。
您可以通过仅使用 datetime
列来解决此问题。这将使用索引并且效率很高。
select *
from gps_logs
where '2018-07-14 00:00:00' <= created_at and created_at < '2018-07-15 00:00:00'
或者您可以将单个 datetime
列拆分为 date
和 time
列,但这会带来新问题。查询跨日边界的范围变得困难。就像也许你想在不同的时区度过一天。单列很容易。
select *
from gps_logs
where '2018-07-12 10:00:00' <= created_at and created_at < '2018-07-13 10:00:00'
但它更多地涉及单独的date
和time
。
select *
from gps_logs
where (created_date = '2018-07-12' and created_time >= '10:00:00')
or (created_date = '2018-07-13' and created_time < '10:00:00');
或者您可以使用partial indexes like Postgresql 切换到数据库。部分索引允许您仅索引值的一部分或函数的结果。而且 Postgresql 在很多事情上都比 MySQL 做得更好。这是我推荐的。
尽可能多地使用 SQL。
例如,如果您想知道每台设备每天有多少日志条目,而不是拉出所有行并自己计算它们,您可以使用group by 按设备和日期对它们进行分组。
select gps_device_id, count(id) as num_entries, created_at::date as day
from gps_logs
group by gps_device_id, day;
gps_device_id | num_entries | day
---------------+-------------+------------
1 | 29310 | 2018-07-12
2 | 23923 | 2018-07-11
2 | 23988 | 2018-07-12
有了这么多数据,您将需要严重依赖 group by
和关联的 aggregate functions,例如 sum
、count
、max
、min
等等。
避免select *
如果您必须检索 86400 行,那么从数据库中获取所有数据的成本可能会很高。您可以通过仅获取所需的列来显着加快速度。这意味着使用select only, the, specific, columns, you, need
而不是select *
。
把它们放在一起。
在 PostgreSQL 中
您在 PostgreSQL 中的架构应该如下所示。
create table gps_devices (
id serial primary key,
name text not null
-- any other columns about the devices
);
create table gps_logs (
id bigserial primary key,
gps_device_id int references gps_devices(id),
created_at timestamp not null default current_timestamp,
latitude numeric(12,9) not null,
longitude numeric(12,9) not null
);
create index timestamp_and_device on gps_logs(created_at, gps_device_id);
create index date_and_device on gps_logs((created_at::date), gps_device_id);
一个查询通常每个表只能使用一个索引。由于您将大量搜索时间戳和设备 ID,timestamp_and_device
结合了时间戳和设备 ID 的索引。
date_and_device
是一回事,但它只是时间戳的日期部分的部分索引。这将使where created_at::date = '2018-07-12' and gps_device_id = 42
非常高效。
在 MySQL 中
create table gps_devices (
id int primary key auto_increment,
name text not null
-- any other columns about the devices
);
create table gps_logs (
id bigint primary key auto_increment,
gps_device_id int references gps_devices(id),
foreign key (gps_device_id) references gps_devices(id),
created_at timestamp not null default current_timestamp,
latitude numeric(12,9) not null,
longitude numeric(12,9) not null
);
create index timestamp_and_device on gps_logs(created_at, gps_device_id);
非常相似,但没有部分索引。因此,您要么需要始终在 where
子句中使用裸 created_at
,要么切换到单独的 date
和 time
类型。
【讨论】:
如果他/她一次只查询一个设备,那么索引列应该是gps_device_is
,然后是created_at
。
@TheImpaler 我的做法涵盖了所有基础。如果他们只查询where gps_device_id = ?
,它将使用外键索引。如果他们只查询where created_at = ?
,它将使用timestamp_and_device
,因为created_at
是第一个。如果他们同时查询where created_at = ? and gps_device_id = ?
,它将使用timestamp_and_device
。
你是绝对正确的。我完全忘记了 MySQL 甚至没有问就为 FK 创建索引。【参考方案2】:
请阅读你的问题,对我来说答案是
只需为纬度和经度创建一个单独的表,并设置您的 ID 外键并将其保存。
【讨论】:
【参考方案3】:在不知道您要运行的确切查询的情况下,我只能猜测最佳结构。话虽如此,您应该瞄准每行使用最少字节数的最佳类型。这应该会使您的查询更快。
例如,您可以使用以下结构:
create table device (
id int primary key not null,
name varchar(20),
someinfo varchar(100)
);
create table location (
device_id int not null,
recorded_at timestamp not null,
latitude double not null, -- instead of varchar; maybe float?
longitude double not null, -- instead of varchar; maybe float?
foreign key (device_id) references device (id)
);
create index ix_loc_dev on location (device_id, recorded_at);
如果您包含准确的查询(命名列),我们可以为它们创建更好的索引。
由于您的查询选择性可能很差,您的查询可能会运行全表扫描。对于这种情况,我更进一步,我为列使用了尽可能小的数据类型,所以它会更快:
create table location (
device_id tinyint not null,
recorded_at timestamp not null,
latitude float not null,
longitude float not null,
foreign key (device_id) references device (id)
);
真的想不出比这更小的东西了。
【讨论】:
请注意,索引recorded_at
对date(recorded_at) = '2017-01-02'
之类的查询没有帮助。
是的,表达式应该在运算符的右侧。也许只使用between
。
现在我意识到查询根本不会(很可能)使用任何索引。【参考方案4】:
我可以向您推荐的最好的方法是使用时间序列数据库来存储和访问时间序列数据。您可以在本地托管任何类型的时间序列数据库引擎,只需将更多资源用于开发其访问方法或使用任何专门的数据库来存储远程信息处理数据,例如this。
【讨论】:
以上是关于具有百万行的数据库表的主要内容,如果未能解决你的问题,请参考以下文章