perf: V0.9.1-0.9.3 性能优化(count/featured 全表→聚合、请求内缓存、二级索引、PSI 未读铃铛)

- V0.9.1 (P0): Model::count() 改 SELECT COUNT(*);新增请求内 identity-map 缓存;
  Product::featured() 改 WHERE+ LIMIT 避免全表
- V0.9.2 (P1): schema.sql 内联二级索引 + install/upgrades/009_perf_indexes.sql 幂等迁移
- V0.9.3 (P1): psi_unread_events() 改 COUNT + JSON_CONTAINS,消除每页 all()+循环
This commit is contained in:
2026-08-08 19:47:35 +08:00
parent a2dac3b095
commit 4d36a284ff
5 changed files with 193 additions and 25 deletions
+13
View File
@@ -167,6 +167,19 @@ if (!function_exists('site_url')) {
if (!subsys_user('psi')) return 0; if (!subsys_user('psi')) return 0;
$uid = (int) ($_SESSION['admin_uid'] ?? 0); $uid = (int) ($_SESSION['admin_uid'] ?? 0);
if ($uid <= 0) return 0; if ($uid <= 0) return 0;
// V0.9.3MySQL 下改用 COUNT + JSON_CONTAINS 聚合,避免每个后台页都 SELECT * 全表拉回再循环计数。
// 语义与旧逻辑一致:统计「当前用户不在 read_by 已读列表」的事件数。
if (\Core\Db::driver() === 'mysql') {
try {
return (int) \Core\Db::query(
"SELECT COUNT(*) FROM psi_events WHERE JSON_CONTAINS(COALESCE(read_by,'[]'), CAST(? AS JSON)) = 0",
[$uid]
)->fetchColumn();
} catch (\Throwable $e) {
// 极端情况(如 read_by 含非法 JSON)回退旧逻辑,保证铃铛数字不报错
}
}
// 文件模式 / MySQL 兜底:无 JSON 函数或异常,保留全表循环(数据量小,可接受)
try { try {
$events = (new \App\Models\PSI\Event())->all(); $events = (new \App\Models\PSI\Event())->all();
} catch (\Throwable $e) { } catch (\Throwable $e) {
+70 -4
View File
@@ -11,6 +11,28 @@ class Model
protected $pk = 'id'; protected $pk = 'id';
protected $orderBy = 'id'; protected $orderBy = 'id';
/* ---------- 请求内缓存(同一请求内重复读取同一查询只走一次 DB/文件) ---------- */
private static $reqCache = [];
private function cacheKey(string $method, ...$args): string
{
return $this->table . '|' . $method . '|' . md5(serialize($args));
}
/** 读取缓存;未命中返回 null(用 array_key_exists 区分「未缓存」与「缓存了空数组」) */
private function cacheGet(string $k)
{
return array_key_exists($k, self::$reqCache) ? self::$reqCache[$k] : null;
}
private function cachePut(string $k, $v): void
{
self::$reqCache[$k] = $v;
}
/** 任意写操作后清空请求内缓存,避免同请求内读到脏数据(CMS 读多写少,刷新成本可忽略) */
private function cacheFlush(): void
{
self::$reqCache = [];
}
/* ---------- 文件模式 ---------- */ /* ---------- 文件模式 ---------- */
private function file(): string private function file(): string
{ {
@@ -71,10 +93,16 @@ class Model
/* ---------- 通用 CRUD ---------- */ /* ---------- 通用 CRUD ---------- */
public function all(): array public function all(): array
{ {
$k = $this->cacheKey('all');
$hit = $this->cacheGet($k);
if ($hit !== null) return $hit;
if (Db::driver() === 'mysql') { if (Db::driver() === 'mysql') {
return Db::query("SELECT * FROM `{$this->table}` ORDER BY `{$this->orderBy}` ASC")->fetchAll(); $rows = Db::query("SELECT * FROM `{$this->table}` ORDER BY `{$this->orderBy}` ASC")->fetchAll();
} else {
$rows = $this->read(); $this->sort($rows);
} }
$rows = $this->read(); $this->sort($rows); return $rows; $this->cachePut($k, $rows);
return $rows;
} }
public function find($id) public function find($id)
@@ -106,6 +134,7 @@ class Model
public function insert(array $data) public function insert(array $data)
{ {
$this->cacheFlush();
if (Db::driver() === 'mysql') { if (Db::driver() === 'mysql') {
$cols = array_keys($data); $cols = array_keys($data);
$sql = "INSERT INTO `{$this->table}` (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")"; $sql = "INSERT INTO `{$this->table}` (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
@@ -121,6 +150,7 @@ class Model
public function update($id, array $data): void public function update($id, array $data): void
{ {
$this->cacheFlush();
if (Db::driver() === 'mysql') { if (Db::driver() === 'mysql') {
$cols = array_keys($data); $cols = array_keys($data);
$sql = "UPDATE `{$this->table}` SET `" . implode('`=?,`', $cols) . "`=? WHERE `{$this->pk}`=?"; $sql = "UPDATE `{$this->table}` SET `" . implode('`=?,`', $cols) . "`=? WHERE `{$this->pk}`=?";
@@ -136,6 +166,7 @@ class Model
public function delete($id): void public function delete($id): void
{ {
$this->cacheFlush();
if (Db::driver() === 'mysql') { if (Db::driver() === 'mysql') {
Db::query("DELETE FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id]); Db::query("DELETE FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id]);
return; return;
@@ -147,6 +178,7 @@ class Model
/** 按任意列批量删除(用于主从表级联删除从表) */ /** 按任意列批量删除(用于主从表级联删除从表) */
public function deleteRaw(string $col, $val): void public function deleteRaw(string $col, $val): void
{ {
$this->cacheFlush();
if (Db::driver() === 'mysql') { if (Db::driver() === 'mysql') {
Db::query("DELETE FROM `{$this->table}` WHERE `{$col}`=?", [$val]); Db::query("DELETE FROM `{$this->table}` WHERE `{$col}`=?", [$val]);
return; return;
@@ -155,8 +187,42 @@ class Model
$this->write(array_values($rows)); $this->write(array_values($rows));
} }
public function count(): int /**
* 计数:MySQL 下走 COUNT(*) 聚合(避免 SELECT * 全表拉回再 count);
* 可选按某列过滤。文件模式下回退为本地计数。
* @param string|null $col 过滤列(null 表示全表计数)
* @param mixed $val 过滤值
*/
public function count(string $col = null, $val = null): int
{ {
return count($this->all()); if (Db::driver() === 'mysql') {
if ($col === null) {
return (int) Db::query("SELECT COUNT(*) FROM `{$this->table}`")->fetchColumn();
}
return (int) Db::query("SELECT COUNT(*) FROM `{$this->table}` WHERE `{$col}`=?", [$val])->fetchColumn();
}
$rows = $this->all();
if ($col === null) return count($rows);
$n = 0;
foreach ($rows as $r) {
if (($r[$col] ?? null) == $val) $n++;
}
return $n;
}
/**
* 取某列等于 $val 的前 $limit 条。
* MySQL 下带 LIMIT(索引友好);文件模式用 whereAll + array_slice 兜底。
* 用于首页精选 / 列表分页等高频只读场景。
*/
public function whereLimit(string $col, $val, int $limit): array
{
if (Db::driver() === 'mysql') {
return Db::query(
"SELECT * FROM `{$this->table}` WHERE `{$col}`=? ORDER BY `{$this->orderBy}` ASC LIMIT ?",
[$val, $limit]
)->fetchAll();
}
return array_slice($this->whereAll($col, $val), 0, $limit);
} }
} }
+3 -1
View File
@@ -14,7 +14,9 @@ class Product extends Model
} }
public function featured(int $limit = 6): array public function featured(int $limit = 6): array
{ {
return array_slice(array_filter($this->all(), fn($p) => ($p['status'] ?? 1) == 1), 0, $limit); // V0.9.1:改用 whereLimit('status',1,$limit)MySQL 下走带 LIMIT 的索引查询,
// 不再 SELECT * 全表拉回再 array_filter(原写法每次请求都全表扫描)。
return $this->whereLimit('status', 1, $limit);
} }
public function specsArray($p): array public function specsArray($p): array
{ {
+52 -20
View File
@@ -10,7 +10,8 @@ CREATE TABLE IF NOT EXISTS `categories` (
`sort_order` INT DEFAULT 0, `sort_order` INT DEFAULT 0,
`status` TINYINT DEFAULT 1, `status` TINYINT DEFAULT 1,
`layout` TEXT, `layout` TEXT,
`mode` VARCHAR(16) NOT NULL DEFAULT 'fixed' `mode` VARCHAR(16) NOT NULL DEFAULT 'fixed',
KEY idx_categories_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `products` ( CREATE TABLE IF NOT EXISTS `products` (
@@ -29,7 +30,10 @@ CREATE TABLE IF NOT EXISTS `products` (
`status` TINYINT DEFAULT 1, `status` TINYINT DEFAULT 1,
`created_at` VARCHAR(20) DEFAULT '', `created_at` VARCHAR(20) DEFAULT '',
`layout` TEXT, `layout` TEXT,
`mode` VARCHAR(16) NOT NULL DEFAULT 'fixed' `mode` VARCHAR(16) NOT NULL DEFAULT 'fixed',
KEY idx_products_category_id (category_id),
KEY idx_products_slug (slug),
KEY idx_products_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `news` ( CREATE TABLE IF NOT EXISTS `news` (
@@ -44,7 +48,9 @@ CREATE TABLE IF NOT EXISTS `news` (
`status` TINYINT DEFAULT 1, `status` TINYINT DEFAULT 1,
`views` INT DEFAULT 0, `views` INT DEFAULT 0,
`layout` TEXT, `layout` TEXT,
`mode` VARCHAR(16) NOT NULL DEFAULT 'fixed' `mode` VARCHAR(16) NOT NULL DEFAULT 'fixed',
KEY idx_news_slug (slug),
KEY idx_news_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `cases` ( CREATE TABLE IF NOT EXISTS `cases` (
@@ -61,7 +67,9 @@ CREATE TABLE IF NOT EXISTS `cases` (
`status` TINYINT DEFAULT 1, `status` TINYINT DEFAULT 1,
`views` INT DEFAULT 0, `views` INT DEFAULT 0,
`layout` TEXT, `layout` TEXT,
`mode` VARCHAR(16) NOT NULL DEFAULT 'fixed' `mode` VARCHAR(16) NOT NULL DEFAULT 'fixed',
KEY idx_cases_slug (slug),
KEY idx_cases_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `pages` ( CREATE TABLE IF NOT EXISTS `pages` (
@@ -71,7 +79,8 @@ CREATE TABLE IF NOT EXISTS `pages` (
`content` TEXT, `content` TEXT,
`layout` TEXT, `layout` TEXT,
`mode` VARCHAR(16) NOT NULL DEFAULT 'fixed', `mode` VARCHAR(16) NOT NULL DEFAULT 'fixed',
`updated_at` VARCHAR(20) DEFAULT '' `updated_at` VARCHAR(20) DEFAULT '',
KEY idx_pages_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `banners` ( CREATE TABLE IF NOT EXISTS `banners` (
@@ -81,7 +90,9 @@ CREATE TABLE IF NOT EXISTS `banners` (
`image` VARCHAR(255) DEFAULT '', `image` VARCHAR(255) DEFAULT '',
`link` VARCHAR(255) DEFAULT '', `link` VARCHAR(255) DEFAULT '',
`sort_order` INT DEFAULT 0, `sort_order` INT DEFAULT 0,
`status` TINYINT DEFAULT 1 `status` TINYINT DEFAULT 1,
KEY idx_banners_status (status),
KEY idx_banners_sort (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `admin_users` ( CREATE TABLE IF NOT EXISTS `admin_users` (
@@ -95,7 +106,8 @@ CREATE TABLE IF NOT EXISTS `admin_users` (
`crm_perms` TEXT, `crm_perms` TEXT,
`psi_perms` TEXT, `psi_perms` TEXT,
`status` TINYINT DEFAULT 1, `status` TINYINT DEFAULT 1,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_admin_users_role (role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/* ===================== CRM 客户管理系统 ===================== */ /* ===================== CRM 客户管理系统 ===================== */
@@ -117,7 +129,8 @@ CREATE TABLE IF NOT EXISTS `crm_customers` (
`status` VARCHAR(20) DEFAULT 'lead', `status` VARCHAR(20) DEFAULT 'lead',
`remark` TEXT, `remark` TEXT,
`owner` VARCHAR(60) DEFAULT '', `owner` VARCHAR(60) DEFAULT '',
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_customers_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `crm_contacts` ( CREATE TABLE IF NOT EXISTS `crm_contacts` (
@@ -130,7 +143,8 @@ CREATE TABLE IF NOT EXISTS `crm_contacts` (
`wechat` VARCHAR(60) DEFAULT '', `wechat` VARCHAR(60) DEFAULT '',
`is_primary` TINYINT DEFAULT 0, `is_primary` TINYINT DEFAULT 0,
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_contacts_customer_id (customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `crm_leads` ( CREATE TABLE IF NOT EXISTS `crm_leads` (
@@ -144,7 +158,8 @@ CREATE TABLE IF NOT EXISTS `crm_leads` (
`probability` TINYINT DEFAULT 0, `probability` TINYINT DEFAULT 0,
`owner` VARCHAR(60) DEFAULT '', `owner` VARCHAR(60) DEFAULT '',
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_leads_customer_id (customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `crm_followups` ( CREATE TABLE IF NOT EXISTS `crm_followups` (
@@ -156,7 +171,9 @@ CREATE TABLE IF NOT EXISTS `crm_followups` (
`way` VARCHAR(20) DEFAULT '', `way` VARCHAR(20) DEFAULT '',
`result` VARCHAR(60) DEFAULT '', `result` VARCHAR(60) DEFAULT '',
`owner` VARCHAR(60) DEFAULT '', `owner` VARCHAR(60) DEFAULT '',
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_followups_customer_id (customer_id),
KEY idx_followups_lead_id (lead_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
/* ===================== 进销存 PSI 系统 ===================== */ /* ===================== 进销存 PSI 系统 ===================== */
@@ -176,7 +193,9 @@ CREATE TABLE IF NOT EXISTS `psi_materials` (
`price` DECIMAL(10,2) DEFAULT 0, `price` DECIMAL(10,2) DEFAULT 0,
`supplier_id` INT DEFAULT 0, `supplier_id` INT DEFAULT 0,
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_materials_supplier_id (supplier_id),
KEY idx_materials_code (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `psi_products` ( CREATE TABLE IF NOT EXISTS `psi_products` (
@@ -195,7 +214,8 @@ CREATE TABLE IF NOT EXISTS `psi_products` (
`cost` DECIMAL(10,2) DEFAULT 0, `cost` DECIMAL(10,2) DEFAULT 0,
`price` DECIMAL(10,2) DEFAULT 0, `price` DECIMAL(10,2) DEFAULT 0,
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_psi_products_code (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `psi_suppliers` ( CREATE TABLE IF NOT EXISTS `psi_suppliers` (
@@ -225,7 +245,9 @@ CREATE TABLE IF NOT EXISTS `psi_purchases` (
`batch_no` VARCHAR(40) DEFAULT '', `batch_no` VARCHAR(40) DEFAULT '',
`expected_at` VARCHAR(20) DEFAULT '', `expected_at` VARCHAR(20) DEFAULT '',
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_purchases_supplier_id (supplier_id),
KEY idx_purchases_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `psi_sales` ( CREATE TABLE IF NOT EXISTS `psi_sales` (
@@ -241,7 +263,8 @@ CREATE TABLE IF NOT EXISTS `psi_sales` (
`region` VARCHAR(40) DEFAULT '', `region` VARCHAR(40) DEFAULT '',
`batch_no` VARCHAR(40) DEFAULT '', `batch_no` VARCHAR(40) DEFAULT '',
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_sales_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `psi_stock_moves` ( CREATE TABLE IF NOT EXISTS `psi_stock_moves` (
@@ -253,14 +276,16 @@ CREATE TABLE IF NOT EXISTS `psi_stock_moves` (
`ref_no` VARCHAR(40) DEFAULT '', `ref_no` VARCHAR(40) DEFAULT '',
`batch_no` VARCHAR(40) DEFAULT '', `batch_no` VARCHAR(40) DEFAULT '',
`remark` TEXT, `remark` TEXT,
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_stock_moves_item (item_type, item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `settings` ( CREATE TABLE IF NOT EXISTS `settings` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
`skey` VARCHAR(120) NOT NULL, `skey` VARCHAR(120) NOT NULL,
`sval` TEXT, `sval` TEXT,
`sgroup` VARCHAR(40) DEFAULT 'site' `sgroup` VARCHAR(40) DEFAULT 'site',
KEY idx_settings_sgroup (sgroup)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `orders` ( CREATE TABLE IF NOT EXISTS `orders` (
@@ -277,7 +302,10 @@ CREATE TABLE IF NOT EXISTS `orders` (
`status` VARCHAR(20) DEFAULT 'pending', `status` VARCHAR(20) DEFAULT 'pending',
`gateway_trade_no` VARCHAR(120) DEFAULT '', `gateway_trade_no` VARCHAR(120) DEFAULT '',
`paid_at` VARCHAR(20) DEFAULT '', `paid_at` VARCHAR(20) DEFAULT '',
`created_at` VARCHAR(20) DEFAULT '' `created_at` VARCHAR(20) DEFAULT '',
KEY idx_orders_order_no (order_no),
KEY idx_orders_status (status),
KEY idx_orders_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `payments` ( CREATE TABLE IF NOT EXISTS `payments` (
@@ -289,7 +317,9 @@ CREATE TABLE IF NOT EXISTS `payments` (
`trade_no` VARCHAR(120) DEFAULT '', `trade_no` VARCHAR(120) DEFAULT '',
`status` VARCHAR(20) DEFAULT '', `status` VARCHAR(20) DEFAULT '',
`created_at` VARCHAR(20) DEFAULT '', `created_at` VARCHAR(20) DEFAULT '',
`paid_at` VARCHAR(20) DEFAULT '' `paid_at` VARCHAR(20) DEFAULT '',
KEY idx_payments_order_id (order_id),
KEY idx_payments_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `psi_events` ( CREATE TABLE IF NOT EXISTS `psi_events` (
@@ -304,5 +334,7 @@ CREATE TABLE IF NOT EXISTS `psi_events` (
`recipients` TEXT, `recipients` TEXT,
`channels` VARCHAR(255) NOT NULL DEFAULT '["inapp"]', `channels` VARCHAR(255) NOT NULL DEFAULT '["inapp"]',
`read_by` TEXT, `read_by` TEXT,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_events_sys (sys),
KEY idx_events_level (level)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+55
View File
@@ -0,0 +1,55 @@
-- V0.9.2 性能优化:为高频查询列补齐二级索引(MySQL 5.7 兼容)
-- 通过存储过程判断索引是否已存在,重复执行不会报错。
-- 部署方式:放入 install/upgrades/ 后,后台「数据库升级」一键应用(仅超级管理员)。
-- 升级记录会写入 db_upgrades,内容不变不会重复执行。
DROP PROCEDURE IF EXISTS _perf_add_idx;
DELIMITER $$
CREATE PROCEDURE _perf_add_idx(IN p_table VARCHAR(64), IN p_name VARCHAR(64), IN p_cols VARCHAR(255))
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table AND INDEX_NAME = p_name
) THEN
SET @_perf_sql = CONCAT('ALTER TABLE `', p_table, '` ADD INDEX `', p_name, '` (', p_cols, ')');
PREPARE _perf_stmt FROM @_perf_sql;
EXECUTE _perf_stmt;
DEALLOCATE PREPARE _perf_stmt;
END IF;
END$$
DELIMITER ;
CALL _perf_add_idx('categories','idx_categories_slug','`slug`');
CALL _perf_add_idx('products','idx_products_category_id','`category_id`');
CALL _perf_add_idx('products','idx_products_slug','`slug`');
CALL _perf_add_idx('products','idx_products_status','`status`');
CALL _perf_add_idx('news','idx_news_slug','`slug`');
CALL _perf_add_idx('news','idx_news_status','`status`');
CALL _perf_add_idx('cases','idx_cases_slug','`slug`');
CALL _perf_add_idx('cases','idx_cases_status','`status`');
CALL _perf_add_idx('pages','idx_pages_slug','`slug`');
CALL _perf_add_idx('banners','idx_banners_status','`status`');
CALL _perf_add_idx('banners','idx_banners_sort','`sort_order`');
CALL _perf_add_idx('admin_users','idx_admin_users_role','`role`');
CALL _perf_add_idx('orders','idx_orders_order_no','`order_no`');
CALL _perf_add_idx('orders','idx_orders_status','`status`');
CALL _perf_add_idx('orders','idx_orders_product_id','`product_id`');
CALL _perf_add_idx('payments','idx_payments_order_id','`order_id`');
CALL _perf_add_idx('payments','idx_payments_order_no','`order_no`');
CALL _perf_add_idx('crm_customers','idx_customers_status','`status`');
CALL _perf_add_idx('crm_contacts','idx_contacts_customer_id','`customer_id`');
CALL _perf_add_idx('crm_leads','idx_leads_customer_id','`customer_id`');
CALL _perf_add_idx('crm_followups','idx_followups_customer_id','`customer_id`');
CALL _perf_add_idx('crm_followups','idx_followups_lead_id','`lead_id`');
CALL _perf_add_idx('psi_materials','idx_materials_supplier_id','`supplier_id`');
CALL _perf_add_idx('psi_materials','idx_materials_code','`code`');
CALL _perf_add_idx('psi_products','idx_psi_products_code','`code`');
CALL _perf_add_idx('psi_purchases','idx_purchases_supplier_id','`supplier_id`');
CALL _perf_add_idx('psi_purchases','idx_purchases_order_no','`order_no`');
CALL _perf_add_idx('psi_sales','idx_sales_order_no','`order_no`');
CALL _perf_add_idx('psi_stock_moves','idx_stock_moves_item','`item_type`,`item_id`');
CALL _perf_add_idx('settings','idx_settings_sgroup','`sgroup`');
CALL _perf_add_idx('psi_events','idx_events_sys','`sys`');
CALL _perf_add_idx('psi_events','idx_events_level','`level`');
DROP PROCEDURE IF EXISTS _perf_add_idx;