PHP+MySQL预约系统自定义格式ID重复问题求助
解决并发下预约编号重复问题
你的问题核心是并发请求时的竞态条件:多个请求同时读取到当天最大的预约ID,各自加1后生成重复的编号。下面是几种可行的解决办法:
方法一:数据库事务+行级锁(基于现有表改造)
利用MySQL的行级锁,确保同一时间只有一个请求能获取并更新最新ID,CodeIgniter 3中可以这么改:
// 开启事务 $this->CI->db->trans_start(); // 加行级锁查询当天最大ID,其他请求会等待锁释放 $this->CI->db->select('reservation_inside_id'); $this->CI->db->where('DATE(reservation_insert_time)', date("Y-m-d")); $this->CI->db->from($this->table); $this->CI->db->order_by('reservation_inside_id DESC'); $this->CI->db->limit(1); // 关键:加上FOR UPDATE锁定查询到的行 $query_sql = $this->CI->db->get_compiled_select(); $query = $this->CI->db->query($query_sql . ' FOR UPDATE'); $inside_id = ''; if ($query->num_rows() > 0) { $row = $query->row(); $some_string = THE_FORMAT; // 你的XXX格式部分 // 拆分原ID的数字部分再自增,避免字符串直接++的隐式转换问题 $num_part = intval(substr($row->reservation_inside_id, strlen($some_string))); $inside_id = $some_string . str_pad($num_part + 1, 2, '0', STR_PAD_LEFT); } else { $inside_id = NEW_ID; // 比如XXX01 } // 提交事务 $this->CI->db->trans_complete(); // 检查事务是否成功 if ($this->CI->db->trans_status() === FALSE) { // 处理事务失败,比如重试或返回错误 return false; } return $inside_id;
说明:FOR UPDATE会锁定查询到的行,其他请求必须等待当前事务提交后才能执行该查询,避免了同时读取相同最大ID的问题。
方法二:单独维护每日计数表(更高效的原子操作)
创建一个专门的表来存储每日的计数,利用MySQL的原子更新操作,彻底避免竞态:
1. 创建计数表
CREATE TABLE daily_reservation_seq ( seq_date DATE PRIMARY KEY, current_count INT DEFAULT 1 ) ENGINE=InnoDB;
2. CodeIgniter 3实现代码
$today = date("Y-m-d"); $some_string = THE_FORMAT; // 你的XXX格式部分 // 原子更新:如果当天记录存在则计数+1,不存在则插入初始值1 $this->CI->db->query(" INSERT INTO daily_reservation_seq (seq_date, current_count) VALUES (?, 1) ON DUPLICATE KEY UPDATE current_count = current_count + 1 ", [$today]); // 获取更新后的计数 $query = $this->CI->db->get_where('daily_reservation_seq', ['seq_date' => $today]); $row = $query->row(); $current_count = $row->current_count; // 拼接成最终预约ID return $some_string . str_pad($current_count, 2, '0', STR_PAD_LEFT);
说明:INSERT ... ON DUPLICATE KEY UPDATE是原子操作,MySQL会保证同一时间只有一个请求能成功更新计数,完全避免并发冲突,性能比行级锁更好。
方法三:用Redis做原子自增(高并发场景首选)
如果你的系统部署了Redis,可以利用它的原子自增命令INCR,性能远高于数据库操作:
// 初始化Redis客户端(CodeIgniter可通过扩展或第三方库实现) $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $today_key = 'reservation_seq_' . date("Ymd"); // 原子自增:不存在则自动初始化为0再加1,返回值是自增后的数值 $current_count = $redis->incr($today_key); // 设置key的过期时间为当天剩余时间,避免内存浪费 $expire_seconds = strtotime(date("Y-m-d 23:59:59")) - time(); $redis->expire($today_key, $expire_seconds); $some_string = THE_FORMAT; return $some_string . str_pad($current_count, 2, '0', STR_PAD_LEFT);
说明:Redis的INCR是单线程原子操作,高并发下也不会出现重复,适合预约量很大的场景。
内容的提问来源于stack exchange,提问作者roev
相关产品推荐
相关产品推荐

