You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Supabase Flutter多UUID查询优化:粉丝数据获取效率提升问询

Supabase + Flutter 粉丝数据查询优化方案

一、表结构重构(核心优化)

你当前用数组存储粉丝/关注UUID的方式不符合SQL设计范式,不仅无法创建有效外键约束,还会导致查询效率低下、分页困难。推荐重构为关联表结构:

1. 废弃原followers和following表,创建统一的user_follows表

CREATE TABLE user_follows (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id UUID REFERENCES users(uuid) ON DELETE CASCADE, -- 被关注者ID
  follower_id UUID REFERENCES users(uuid) ON DELETE CASCADE, -- 粉丝ID
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
  • 通过user_id可查询某用户的所有粉丝,通过follower_id可查询某用户关注的所有人
  • 天然支持外键约束,避免无效UUID数据
  • 后续分页、排序操作更灵活

二、查询逻辑优化

1. 重构后的查询(一次请求获取所有粉丝元数据)

无需先拿UUID再循环查询,直接用JOIN关联users表一次性获取数据:

/// 存储粉丝Profile数据
List<ProfileModel> followersProfiles = [];

/// 一次性获取当前用户的所有粉丝元数据
Future<void> fetchFollowersProfiles() async {
  followersProfiles.clear();
  try {
    final data = await supabase
        .from('user_follows')
        .select('follower_id, users!inner(*)') // 关联查询粉丝的用户信息
        .eq('user_id', _currentUuid)
        .order('created_at', ascending: false); // 按关注时间倒序

    if (data != null && data.isNotEmpty) {
      followersProfiles = data.map((item) => ProfileModel.fromMap(item['users'])).toList();
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }
  update();
}

2. 过渡方案(暂时不改表结构)

用IN操作符一次性查询所有UUID对应的用户数据,避免循环请求:

Future<void> fetchFollowersProfiles() async {
  followersProfiles.clear();
  try {
    // 先获取粉丝UUID数组
    final followerData = await supabase
        .from('followers')
        .select('followers_list')
        .eq('user_id', _currentUuid)
        .single();
    
    if (followerData != null && followerData['followers_list'].isNotEmpty) {
      final uuids = followerData['followers_list'];
      // 一次性查询所有UUID对应的用户
      final userData = await supabase
          .from('users')
          .select('*')
          .in_('uuid', uuids);
      
      followersProfiles = userData.map((item) => ProfileModel.fromMap(item)).toList();
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }
  update();
}

三、高效分页实现

1. Offset-Limit分页(适合小数据量)

int _currentPage = 0;
final int _pageSize = 20;

Future<void> fetchFollowersPage() async {
  try {
    final data = await supabase
        .from('user_follows')
        .select('follower_id, users!inner(*)')
        .eq('user_id', _currentUuid)
        .order('created_at', ascending: false)
        .range(_currentPage * _pageSize, (_currentPage + 1) * _pageSize - 1);
    
    if (data.isNotEmpty) {
      followersProfiles.addAll(data.map((item) => ProfileModel.fromMap(item['users'])).toList());
      _currentPage++;
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }
  update();
}

在ListView中监听滚动到底部时调用fetchFollowersPage()加载下一页。

2. Keyset分页(适合大数据量,性能更优)

利用created_at或uuid作为游标,避免Offset分页的性能损耗:

DateTime? _lastFollowerCreatedAt;
final int _pageSize = 20;

Future<void> fetchFollowersPage() async {
  try {
    var query = supabase
        .from('user_follows')
        .select('follower_id, users!inner(*), created_at')
        .eq('user_id', _currentUuid)
        .order('created_at', ascending: false)
        .limit(_pageSize);
    
    // 非第一页时添加游标条件
    if (_lastFollowerCreatedAt != null) {
      query = query.lt('created_at', _lastFollowerCreatedAt);
    }
    
    final data = await query;
    
    if (data.isNotEmpty) {
      followersProfiles.addAll(data.map((item) => ProfileModel.fromMap(item['users'])).toList());
      // 更新游标为最后一条数据的创建时间
      _lastFollowerCreatedAt = DateTime.parse(data.last['created_at']);
    }
  } catch (e) {
    if (kDebugMode) {
      print(e);
    }
  }
  update();
}

四、Widget代码适配

原Widget无需大幅修改,确保传入的usersList为优化后的followersProfiles即可:

class FollowersAndFollowingTab extends StatelessWidget {
  final List<ProfileModel> usersList;

  const FollowersAndFollowingTab({super.key, required this.usersList});

  @override
  Widget build(BuildContext context) {
    return GetBuilder<DBController>(
      builder: (_) => ListView.builder(
        shrinkWrap: true,
        itemCount: usersList.length,
        itemBuilder: (context, index) {
          final userProfile = usersList[index];
          return Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8.0),
            child: Card(
              child: ListTile(
                onTap: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    fullscreenDialog: true,
                    builder: (context) => ProfileViewScreen(profileModel: userProfile),
                  ),
                ),
                leading: Container(
                  width: 50,
                  decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(8),
                    image: DecorationImage(
                      fit: BoxFit.cover,
                      image: CachedNetworkImageProvider(userProfile.profilePhoto!),
                    ),
                  ),
                ),
                title: Text(
                  '${_bioWidgets.fullNameNoFlag(profileModel: userProfile)} ${_bioWidgets.returnFlagEmoji(profileModel: userProfile)}',
                ),
                subtitle: Text('@${userProfile.username!}'),
                trailing: IconButton(
                  onPressed: () {},
                  icon: const Icon(Icons.more_vert, color: Colors.grey),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Raj A

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 17:47:05