如何使用 PHP 和 PDO 计算 MySQL 表中行子集的大小(以字节为单位)?

Posted

技术标签:

【中文标题】如何使用 PHP 和 PDO 计算 MySQL 表中行子集的大小(以字节为单位)?【英文标题】:How to calculate the size (in byte) of a subset of rows from a MySQL table using PHP with PDO? 【发布时间】:2013-02-17 00:17:13 【问题描述】:

首先,我正在使用带有 PDO 扩展的 php 5.4.11 和 mysql 5.1.66(在 Debian Squeeze 上)开发共享托管服务。

目前我正在开发一项服务,其中用户在数据库中存储数据的配额有限。目前,只有一张表存储用户数据,必须遵守有关配额的规定(但这可能会改变)。所有表都使用 InnoDB 存储引擎和文本列的 utf8_unicode_ci 排序规则。假设与配额相关的表具有以下列:

+--------------+-----------+
| Column name  |   Type    |
+--------------+-----------+
| id           | int       |
| userId       | int       |
| created      | timestamp |
| lastModified | timestamp |
| description  | varchar   |
| content      | text      |
+--------------+-----------+

现在我需要计算属于特定用户的所有行的大小(以字节为单位)。我搜索了文档并在 Google 上四处搜索,但只发现其他人提出了类似的问题而没有得到令人满意的答案。

我知道 MySQL LENGTH() 函数,但由于它是一个字符串函数,它不会返回(固定长度)数字和日期/时间字段占用的空间。如果只考虑字符串字段,用户可能只是用空字符串填满数据库,永远不会达到他的配额。我也知道 MySQL 中每行的描述都有一些开销,但我不想将其包含在计算中。 (作为等价物,我想计算实际文件大小,而不是磁盘上的文件大小。)

此外,我不想依赖特定的表结构,因为这可能会改变,并且必须记住更新计算配额的函数。

由于缺乏现有的解决方案,我想出了自己的解决方案(请参阅下文)。但它也有一些缺点,例如:

它需要表格中使用的数据类型及其各自大小的列表。 它无法准确处理FLOAT(p)DECIMAL(M,D)NUMERIC(M,D)BIT(M) 数据类型(尽管可以实现)。 它需要两个单独的查询。

所以现在,这就是我想出的:

$db = new PDO(...);
$tablename = 'users';
$userId = 1;

// Make a list of type sizes in bytes - null indicates string types of
// varying size. If there is a type used in the database which is not
// listed here, an exception will be thrown.
$typeSizes = array(
    'int' => 4,
    'timestamp' => 4,
    'varchar' => null,
    'text' => null
  );

// Get datatypes used in the table.
$sql = 'SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS '
     . 'WHERE TABLE_NAME=?';
$stmt = $db->prepare($sql);
$stmt->bindValue(1, $tablename);
$stmt->execute();
$colTypes = array_map('reset', array_map('reset',
                       $stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC)));

// Iterate over the existing columns. Sum up sizes of fixed size columns to
// get a 'fixed-size-factor' for a row. Make a list of varying size columns.
$fixedSizeFactor = 0;
$varyingSizeCols = array();
foreach ($colTypes as $colName => $colType) 
  if (array_key_exists($colType, $typeSizes)) 
    if ($typeSizes[$colType] !== null) 
      $fixedSizeFactor += $typeSizes[$colType];
     else 
      $varyingSizeCols[] = $colName;
    
   else 
    $msg = "Unhandled column type '$colType' - unable to calculate used "
         . 'storage. Probably the $typeSizes array needs to be updated.';
    throw new Exception($msg);
  


// Get number of all records of the user and the size of his data in
// varying size columns.
$sumArgument = 0;
if (count($varyingSizeCols) > 0) 
  $sumArgument = 'LENGTH(' . implode(') + LENGTH(', $varyingSizeCols) . ')';

$sql = 'SELECT SUM(' . $sumArgument . ') AS size, COUNT(*) AS count FROM '
     . $tablename . ' WHERE userId=?';
$stmt = $db->prepare($sql);
$stmt->bindValue(1, $userId);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);

// Calculate used storage.
$usedStorage = $result['count'] * $fixedSizeFactor + $result['size'];

我的问题是:有没有更“原生”、不那么骇人听闻的方式来做到这一点?如果没有,您对性能优化有什么建议吗?

【问题讨论】:

【参考方案1】:

忘记数字和日期,真的,如果你因为这些字段而限制用户,那真是太便宜了......

使用LENGTH(用于文本)和OCTET_LENGTH(用于blob)方法就足够了。

如果您的存储空间确实不足,并且必须按用户准确划分,请不要忘记还有日志管理会增加磁盘空间,并且这些取决于用户对您的数据库的使用情况。 .

【讨论】:

如果 OP 真的要按数据字节收费,别忘了加上数据库索引的开销 那么你如何让舒尔用户不插入大量的空文本行? (对于我的应用程序来说,空文本是完全可以的。)只是有一个取决于用户配额的最大条目数?顺便说一句:根据 MySQL 文档,LENGTH()OCTET_LENGTH() 是同义词。 你在这个表中存储了什么样的数据?根据您的表定义,除了文本列之外,您不应该关心这个问题。现实一点,如果你在那里有存储问题,你就有麻烦了。即使您突然有 100 万条空记录,它会对您的数据库产生什么影响?重新考虑您的基础架构要求...

以上是关于如何使用 PHP 和 PDO 计算 MySQL 表中行子集的大小(以字节为单位)?的主要内容,如果未能解决你的问题,请参考以下文章

php+mysql 请问:用pdo如何获取某个表中记录的数目?

如何使用 PHP PDO 检查 MySQL 中是不是存在表? [复制]

使用 PHP/PDO 将大型 MySQL 表导出为 CSV

php PDO 和 mysql:如何插入地理点类型?

如何计算 MySQL 表中的行数(PHP PDO)

如何在PHP下开启PDO MySQL的扩展