mirror of
https://github.com/tnb-labs/panel.git
synced 2026-08-29 02:10:58 +08:00
fix: 优化数据库迁移
This commit is contained in:
@@ -57,8 +57,5 @@ func NewDB(conf *config.Config) (*gorm.DB, error) {
|
||||
}
|
||||
|
||||
func NewMigrate(db *gorm.DB) (*gormigrate.Gormigrate, error) {
|
||||
|
||||
return gormigrate.New(db, &gormigrate.Options{
|
||||
UseTransaction: true, // Note: MySQL not support DDL transaction
|
||||
}, migration.Migrations), nil
|
||||
return gormigrate.New(db, nil, migration.Migrations), nil
|
||||
}
|
||||
|
||||
@@ -79,3 +79,18 @@ func batchUpsert[T any](db *gorm.DB, items []T, conflict clause.OnConflict) erro
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// vacuumDB 清理数据后回收文件空间
|
||||
func vacuumDB(db *gorm.DB) error {
|
||||
if err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 写回 VACUUM 结果并截断文件
|
||||
if err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Exec("PRAGMA optimize").Error
|
||||
}
|
||||
|
||||
@@ -48,11 +48,5 @@ func (r *monitorRepo) List(start, end time.Time) ([]*biz.Monitor, error) {
|
||||
}
|
||||
|
||||
func (r *monitorRepo) VacuumDB() error {
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.Exec("PRAGMA optimize").Error
|
||||
return vacuumDB(r.db)
|
||||
}
|
||||
|
||||
@@ -173,13 +173,7 @@ func (r *scanEventRepo) VacuumDB() error {
|
||||
if err := r.db.Exec("DELETE FROM scan_sources WHERE id NOT IN (SELECT source_id FROM scan_events)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.Exec("PRAGMA optimize").Error
|
||||
return vacuumDB(r.db)
|
||||
}
|
||||
|
||||
// parseTimeStr 解析 Go time.String() 格式并转为 RFC3339
|
||||
|
||||
@@ -89,11 +89,5 @@ func (r *tamperRepo) ClearLogsBefore(t time.Time) error {
|
||||
}
|
||||
|
||||
func (r *tamperRepo) VacuumDB() error {
|
||||
if err := r.logDB.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.logDB.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.logDB.Exec("PRAGMA optimize").Error
|
||||
return vacuumDB(r.logDB)
|
||||
}
|
||||
|
||||
@@ -153,13 +153,7 @@ func (r *websiteStatRepo) DeleteBySite(site string) error {
|
||||
}
|
||||
|
||||
func (r *websiteStatRepo) VacuumDB() error {
|
||||
if err := r.db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.db.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.Exec("PRAGMA optimize").Error
|
||||
return vacuumDB(r.db)
|
||||
}
|
||||
|
||||
// ========== 蜘蛛统计 ==========
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
var Migrations []*gormigrate.Migration
|
||||
|
||||
const batchSize = 100
|
||||
const batchSize = 1000
|
||||
|
||||
func vacuumDB(db *gorm.DB) error {
|
||||
if err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
@@ -16,5 +16,9 @@ func vacuumDB(db *gorm.DB) error {
|
||||
if err := db.Exec("VACUUM").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 写回 VACUUM 结果并截断文件
|
||||
if err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Exec("PRAGMA optimize").Error
|
||||
}
|
||||
|
||||
+51
-27
@@ -20,50 +20,74 @@ var ScanMigrations = []*gormigrate.Migration{
|
||||
{
|
||||
ID: "20260814-normalize-scan-events",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if !tx.Migrator().HasColumn("scan_events", "source_ip") {
|
||||
legacy := tx.Migrator().HasTable("scan_events_legacy")
|
||||
if !legacy && !tx.Migrator().HasColumn("scan_events", "source_ip") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return tx.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Migrator().RenameTable("scan_events", "scan_events_legacy"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("DROP INDEX IF EXISTS idx_scan_unique").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("DROP INDEX IF EXISTS idx_scan_date").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.AutoMigrate(&biz.ScanSource{}, &biz.ScanEvent{}); err != nil {
|
||||
if !legacy {
|
||||
if err := tx.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Migrator().RenameTable("scan_events", "scan_events_legacy"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("DROP INDEX IF EXISTS idx_scan_unique").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Exec("DROP INDEX IF EXISTS idx_scan_date").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.AutoMigrate(&biz.ScanSource{}, &biz.ScanEvent{})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Exec(`
|
||||
INSERT INTO scan_sources (source_ip, country, region, city, isp)
|
||||
SELECT source_ip, MAX(country), MAX(region), MAX(city), MAX(isp)
|
||||
FROM scan_events_legacy
|
||||
WHERE 1 = 1
|
||||
GROUP BY source_ip
|
||||
ON CONFLICT(source_ip) DO UPDATE SET
|
||||
country = excluded.country,
|
||||
region = excluded.region,
|
||||
city = excluded.city,
|
||||
isp = excluded.isp
|
||||
`).Error; err != nil {
|
||||
// 同一 IP 的归属信息取任意一条,扫描来源数量远小于事件数量
|
||||
if err := tx.Exec(`
|
||||
INSERT INTO scan_sources (source_ip, country, region, city, isp)
|
||||
SELECT source_ip, MAX(country), MAX(region), MAX(city), MAX(isp)
|
||||
FROM scan_events_legacy
|
||||
GROUP BY source_ip
|
||||
ON CONFLICT DO NOTHING
|
||||
`).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 按主键分批提交
|
||||
var cursor uint
|
||||
if err := tx.Raw("SELECT COALESCE(MAX(id), 0) FROM scan_events").Scan(&cursor).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
var next uint
|
||||
if err := tx.Raw(
|
||||
"SELECT COALESCE(MAX(id), 0) FROM (SELECT id FROM scan_events_legacy WHERE id > ? ORDER BY id LIMIT ?)",
|
||||
cursor, batchSize,
|
||||
).Scan(&next).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if next == 0 {
|
||||
break
|
||||
}
|
||||
if err := tx.Exec(`
|
||||
INSERT INTO scan_events (id, source_id, port, protocol, date, count, first_seen, last_seen)
|
||||
SELECT events.id, sources.id, events.port, events.protocol, events.date,
|
||||
events.count, events.first_seen, events.last_seen
|
||||
FROM scan_events_legacy AS events
|
||||
JOIN scan_sources AS sources ON sources.source_ip = events.source_ip
|
||||
`).Error; err != nil {
|
||||
WHERE events.id > ? AND events.id <= ?
|
||||
ON CONFLICT DO NOTHING
|
||||
`, cursor, next).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
cursor = next
|
||||
}
|
||||
|
||||
return tx.Migrator().DropTable("scan_events_legacy")
|
||||
})
|
||||
if err := tx.Migrator().DropTable("scan_events_legacy"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return vacuumDB(tx)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -24,14 +24,17 @@ var TamperMigrations = []*gormigrate.Migration{
|
||||
}
|
||||
}
|
||||
|
||||
var cursor uint
|
||||
for {
|
||||
var items []*biz.TamperLog
|
||||
if err := tx.Where("typeof(path) = 'text'").Order("id").Limit(batchSize).Find(&items).Error; err != nil {
|
||||
if err := tx.Where("id > ? AND typeof(path) = 'text'", cursor).
|
||||
Order("id").Limit(batchSize).Find(&items).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = items[len(items)-1].ID
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"path"}),
|
||||
|
||||
@@ -162,7 +162,10 @@ func init() {
|
||||
if !tx.Migrator().HasTable("monitors") {
|
||||
return nil
|
||||
}
|
||||
return tx.Migrator().DropTable("monitors")
|
||||
if err := tx.Migrator().DropTable("monitors"); err != nil {
|
||||
return err
|
||||
}
|
||||
return vacuumDB(tx)
|
||||
},
|
||||
})
|
||||
Migrations = append(Migrations, &gormigrate.Migration{
|
||||
|
||||
@@ -22,15 +22,18 @@ var WebsiteStatMigrations = []*gormigrate.Migration{
|
||||
{
|
||||
ID: "20260814-compress-website-error-logs",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
var cursor uint
|
||||
for {
|
||||
var items []*biz.WebsiteErrorLog
|
||||
if err := tx.Where("typeof(uri) = 'text' OR typeof(ua) = 'text' OR typeof(body) = 'text'").
|
||||
Order("id").Limit(batchSize).Find(&items).Error; err != nil {
|
||||
if err := tx.Where(
|
||||
"id > ? AND (typeof(uri) = 'text' OR typeof(ua) = 'text' OR typeof(body) = 'text')", cursor,
|
||||
).Order("id").Limit(batchSize).Find(&items).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
break
|
||||
}
|
||||
cursor = items[len(items)-1].ID
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"uri", "ua", "body"}),
|
||||
|
||||
Reference in New Issue
Block a user