【发布时间】:2020-04-18 09:00:27
【问题描述】:
背景
我正在为网络应用程序开发一个网络平台。该平台的关键功能之一是用户管理。因此,我实现了一个登录系统,使注册用户能够登录平台和应用程序。这是使用 MySQL 数据库实现的,该数据库包含登录凭据、用户 ID、联系信息等...
问题
我遇到的问题是用户表。该表可能会非常大。因此,如果管理员想要编辑特定用户的信息,取决于用户在列表中的位置,管理员将不得不滚动浏览可能的数千条记录以找到正确的记录。我正在寻找一种算法,可以将其分成大约 N 大小的组,并将这些组显示为管理员可以点击的范围:#0-B、C-F、G-L、M-R 等......
研究
这是我目前所做的研究:
Database partition - Better done by PHP or MySQL?
1 very large table or 3 large table? MySQL Performance
关于分区,我在网上找到了很多文章来对数据库表进行分区,但没有任何内容能解决我想要做的事情。
到目前为止我做了什么
我编写了一个从 Web 服务器离线运行的程序,它返回每个存储桶 0-9 和 A-Z 的计数。对于小于阈值的计数,它将存储桶组合成一个范围并将其保存到数据库中。因此,如果桶 0-9 和 A、B 的总计数小于阈值,则范围变为 #0-B。如果桶的大小大于阈值,我在试图弄清楚如何从桶中获取范围时遇到问题。
我考虑过以下解决方案:
- 一个递归函数,它不断向下钻取用户名,直到它在阈值范围内。如果我可以让它工作,这将是理想的。
- 由于这是脱机运行,我正在考虑的另一个解决方案是将整个存储桶加载到内存中并从那里拆分。这样做的缺点是可能会占用内存。
- 另一个有趣的想法是#2 的变体,它将存储桶的一部分加载到内存中并对其进行处理。这会将内存使用量减少到或多或少的固定量,但需要更多时间来处理。
编辑 2020 年 5 月 11 日:
基于唯一答案和 cmets,我可以有一个搜索字段,当条目数缩小到阈值以下时,我可以使用 JSON 填充列表(这是个好主意)。但是,我确实有代码,我将与您分享:
此代码有效,但使用测试数据,它会在分区表中创建约 17,500 个条目,并且有相当多的条目计数为零。
function part_recurse2($prefix)
{
global $CONFIGVAR;
global $charList;
global $charCount;
$list = dbCountScan($prefix);
for ($i = 0; $i < count($list); $i++)
{
$char = substr($charList, $i, 1);
if ($list[$i] >= $CONFIGVAR['user_partition_max_size']['value'])
{
part_recurse2($prefix . $char);
}
else
{
writeRange($prefix . $char, $prefix . $char, $list[$i],
substr($prefix . $char, 0, 1));
}
}
}
还有来自数据库的结果...
mysql> select sum(`count`) from userpart;
+--------------+
| sum(`count`) |
+--------------+
| 100004 |
+--------------+
1 row in set (0.01 sec)
mysql> select count(*) from userpart where count = 0;
+----------+
| count(*) |
+----------+
| 1139 |
+----------+
1 row in set (0.01 sec)
此代码部分有效,但计数不相加,并且其中也有零。正确的用户计数是 100,004,但下面的函数产生的总计数是 105,234,这比实际用户表中的条目多 5,230 个。这个版本的好处是它将组合范围直到达到阈值。这就是我想要开始的工作。
function part_recurse($prefix)
{
global $charCount;
global $charList;
global $CONFIGVAR;
$list = dbCountScan($prefix);
$acc = 0;
$start = substr($charList, 0, 1);
for ($i = 0; $i < count($list); $i++)
{
if ($list[$i] == 0) continue;
if ($list[$i] > $CONFIGVAR['user_partition_max_size']['value'])
{
// If the number of entries > threshold...
if ($i == 0)
{
// Only for the first bucket.
$start = substr($charList, 1, 1);
}
else
{
// All other entries.
if ($i >= $charCount - 1)
{
// Last entry
$end = substr($charList, $i - 1, 1);
$acc += $list[$i];
writeRange($prefix . $start, $prefix . $end, $acc,
substr($prefix . substr($charList, $i, 1), 0, 1));
$acc = 0;
}
else
{
$end = substr($charList, $i - 1, 1);
writeRange($prefix . $start, $prefix . $end, $acc,
substr($prefix . substr($charList, $i + 1, 1), 0, 1));
$acc = 0;
$start = substr($charList, $i, 1);
}
}
part_recurse($prefix . substr($charList, $i, 1));
}
else
{
if (($acc + $list[$i]) >= $CONFIGVAR['user_partition_max_size']['value'])
{
$end = substr($charList, $i - 1, 1);
writeRange($prefix . $start, $prefix . $end, $acc,
substr($prefix . substr($charList, $i, 1), 0, 1));
$start = substr($charList, $i, 1);
$acc = $list[$i];
}
else
{
$acc += $list[$i];
}
}
}
// Write the final entry.
if ($acc > 0)
{
$end = substr($charList, $charCount - 1, 1);
$bucket = substr($prefix . substr($charList, $i, 1), 0, 1);
writeRange($prefix . $start, $prefix . $end, $acc, $bucket);
}
}
以及它的数据库结果:
mysql> select sum(`count`) from userpart;
+--------------+
| sum(`count`) |
+--------------+
| 105234 |
+--------------+
1 row in set (0.00 sec)
mysql> select count(*) from userpart where count = 0;
+----------+
| count(*) |
+----------+
| 316 |
+----------+
1 row in set (0.00 sec)
正确的条目数是 100,004,没有零计数。我会不断完善代码,但如果有人看到我做错了什么,请赐教。书外函数具有以下属性:
dbCountScan($prefix): 此函数使用 SELECT COUNT(*) FROM USERS WHERE username like '?% 遍历字符 0-9 和 A-Z ';在哪里 ?是 $prefix 和 for 循环中当前字母的串联。
WriteRange($start, $end, $count, $bucket): 该函数将范围写入数据库。 $start 和 $end 是范围的开始和结束。 $count 是范围内的条目数。而 $bucket 是这个范围所属的顶级存储桶(0-9,A-Z)。
这是数据库表用户部分:
CREATE TABLE `userpart` (
`rangest` varchar(16) NOT NULL COMMENT 'Database query starting range.',
`rangeed` varchar(16) NOT NULL COMMENT 'Database query ending range.',
`count` int unsigned NOT NULL COMMENT 'Count in range.',
`bucket` varchar(5) DEFAULT NULL COMMENT 'Primary bucket.'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Table that is used to partition usernames.'
【问题讨论】:
-
这不就是简单的分页吗?
-
“如果管理员想要编辑特定用户的信息”...您不打算提供搜索功能以用户友好的方式启用这种功能吗?正如草莓所说,您所描述的其余内容似乎只是常规分页。我认为您可能过度设计了您提出的解决方案。
-
@Strawberry 如果系统上有 100,000 个用户,那么分页确实行不通。
-
@ADyson 我没有考虑包含搜索字段。我已经弄清楚了它是如何工作的。至于分页,我不确定系统上有 100,000 个帐户是否可行。即使是 10,000 也无法完全控制。
-
随着用户数量的增加,我认为任何类型的列表都可能是徒劳的,无论是分区、分页还是其他。搜索功能(可能相对灵活,因此它可以根据属性组合和字段的部分匹配等找到用户)对我来说似乎是最好的选择。
标签: php mysql partitioning