首先:这不是我编写点击计数器的方式。
也就是说,100 个用户同时访问您的服务器(初始点击为 0)可能会导致记录的数量为 1..100,其中低(=错误)值很突出。
- 如果您想计入文本文件,请按照@lanzz 的答案锁定并为重大性能损失做好准备 - 您正在有效地序列化请求。
- 如果您想计入任何文件,请考虑使用 SQlite 并为可管理的性能冲击做好准备
- 如果您只想数数,请考虑性能影响很小的真实数据库
编辑:实现
我为文本文件、SQLite 和 MySQL 中的文件计数器创建了以下实现
请不要因为使用mysql_*()
函数系列而激怒我——因为代码总是具有指导性的,而不是富有成效的:在专注于手头的问题而不是周围层的意义上是有指导性的。
计数器文件.php:
<?php
//acquire file handle
$fd=fopen('counter.txt','c+b');
if (!$fd) die("Can't acquire file handle");
//lock the file - we must do this BEFORE reading, as not to read an outdated value
if (!flock($fd,LOCK_EX)) die("Can't lock file");
//read and sanitize the counter value
$counter=fgets($fd,10);
if ($counter===false) die("Can't read file");
if (!is_numeric($counter)) {
flock($fd,LOCK_UN);
die("Value in file '$counter' is not numeric");
}
//increase counter and reconvert to string
$counter++;
$counter="$counter";
//Write to file
if (!rewind($fd)) {
flock($fd,LOCK_UN);
die("Can't rewind file handle");
}
$num=fwrite($fd,$counter);
if ($num!=strlen($counter)) {
flock($fd,LOCK_UN);
die("Error writing file");
}
//Unlock the file and close file handle
flock($fd,LOCK_UN);
fclose($fd);
printf ("Counter is now %05d",$counter);
?>
反sqlite.php:
<?php
//counter.sqlite3 was created with
//CREATE TABLE counter (counter NUMERIC)
//INSERT INTO counter VALUES (0)
//Open database
$dsn='sqlite:'.dirname(__FILE__).'/counter.sqlite3';
$db=new PDO($dsn);
if (!$db) die("Can't open SQlite database via DBO");
//Make exclusive
$sql="BEGIN EXCLUSIVE TRANSACTION";
if ($db->exec($sql)===false) die("Error starting exclusive transaction");
//Update counter
$sql="UPDATE counter SET counter=counter+1";
if (!$db->exec($sql)) die("Error inserting into database");
//Read value
$sql="SELECT counter FROM counter";
$result=$db->query($sql);
if (!$result) die("Error querying database");
foreach ($result as $row) $counter=$row['counter'];
//Commit
$sql="COMMIT TRANSACTION";
if (!$db->exec($sql)) die("Error committing to database");
//Print result
printf("Counter is now %05d",$counter);
?>
反mysql.php:
<?php
//mysql database was created with
//CREATE TABLE counter (counter INT NOT NULL)
//INSERT INTO counter VALUES (0)
//Open database connection and select database
$db=mysql_pconnect('127.0.0.1','redacted','redacted');
if (!$db) die("Can't open database");
if (!mysql_select_db('redacted', $db)) die("Can't select database");
//Update counter
$sql="UPDATE counter SET counter=counter+1";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error updating database");
//Read value
$sql="SELECT counter FROM counter";
$qry=mysql_query($sql,$db);
if (!$qry) die("Error reading from database");
$counter=mysql_fetch_array($qry,MYSQL_ASSOC);
if (!$counter) die("Error reading result");
//Print result
printf("Counter is now %05d",$counter['counter']);
?>
至于表现:我的立场是正确的。SQLite 实现比其他两个慢 100 倍 - 这是因为我不得不接受,除了计算 1000 次点击START EXCLUSIVE TRANSACTION
来结束测试之外,别无其他。ab -n 1000 -c 50 http://127.0.0.1/stackoverflow/counter/counter-sqlite.php
我对 OP 的建议是使用 MySQL 版本 - 它速度快,并且会在操作系统崩溃时可靠地保存计数器。文件版本具有几乎相同的性能特征,但它很容易被操作系统崩溃破坏。