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

时间:2013-03-02 14:36:25

标签: php mysql pdo size quota

首先,我正在使用PHP 5.4.11与PDO扩展和MySQL 5.1.66(在Debian Squeeze上)共享托管服务。

目前,我正在开发一种服务,其中用户在数据库中存储数据的配额有限。目前,只有一个存储userdata的表,必须遵守配额(但可能会发生变化)。所有表都使用InnoDB存储引擎和文本列的utf8_unicode_ci排序规则。让我们假设与配额相关的表格包含以下列:

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

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

我知道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'];

我的问题是:是否有更多的本地',更少的hackish方式来做到这一点?如果没有,您对性能优化有任何建议吗?

1 个答案:

答案 0 :(得分:0)

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

使用LENGTH(对于文本)和OCTET_LENGTH(对于blob)方法,这应该足够了。

如果您的存储空间非常短缺,并且您必须按照用户进行精确划分,请不要忘记还会增加磁盘空间的日志管理,这取决于用户在数据库中的使用情况。