【问题标题】:MySQL Optimization for Queries with Functions in Where CLause使用 Where Clause 中的函数查询 MySQL 优化
【发布时间】:2018-01-25 03:49:01
【问题描述】:

假设我有一张员工表(员工),其中有数千条记录。我需要根据您的登录限制您可以看到的员工以实现可重用性我创建了一个函数fn_HasEmployeeAccess(t_userid int, t_employee_id INT),它将返回 true/false 以确定您是否有权访问该员工。

这个函数看起来像这样(尽管它会进行其他检查,例如,您无法访问自己的个人资料,您无法访问与您共享职位的其他员工等):

 FUNCTION fn_HasEmployeeAccess(t_userid INT, t_employee_id INT)
             DECLARE isAdmin BOOL;
             DECLARE isDirector BOOL;
             DECLARE empStatus INT;

             SELECT admin, director INTO isAdmin, isDirector FROM users 
             WHERE users.userid=t_userid;

             /*select the status of this user if it is in your dept
             if employee is not in your department - status will be null 
             which will fail the checks below and return 0*/
             SELECT employee.status INTO empStatus FROM employee 
             INNER JOIN users ON employee.department = users.department
            WHERE employee.employeeID=t_employee_id;

             IF empStatus IN (1,2) AND isAdmin THEN
                     RETURN 1;
             ELSE IF empStatus=2 AND userIsDirector THEN
                     RETURN 1;
            ELSE
                     return 0;
            END IF;
    END FUNCTION

现在所有这些都有效并且很棒,除非您有大量数据并且需要使用多个连接进行查询,然后根据此函数的结果在该过滤器之上进行查询 - 性能会下降很多,我相信我理解 - 该函数正在为员工表中的每一行运行。

例如:

SELECT * FROM employee e
INNER JOIN employee_attachments a ON e.employeeID = a.employeeID
WHERE fn_HasEmployeeAccess(12345, e.employeeID)=1

如何防止我认为由于函数使用而导致的全表扫描?他们是这种模式的替代品吗?

该功能用于过滤应用程序中几乎所有地方(仪表板、视图、报告)等,如果我想调整对员工的访问权限,我目前只需要调整该功能。有人建议我只内联所有这些逻辑,但每次我们添加状态或想要更改访问逻辑时,这将需要涉足系统以更改与员工有关的每个查询、每个报告、每个部分。

另外 - 我听说函数可以利用索引,但前提是函数是确定性的。这样的函数会是确定性的吗?给定相同的employeeID 和userID,它应该返回相同的结果……除非用户从管理员更改为主管……否则该函数将返回不同的结果。这个函数是确定性的还是非确定性的?

感谢任何见解

【问题讨论】:

  • status 在哪个表中?使用 JOIN 时,请限定所有列名。
  • 抱歉 - 状态在员工表中,表示这是否是已终止员工、在职员工等(employee.status)- 已更新

标签: mysql database-design database-optimization


【解决方案1】:

在较新版本的 MySQL/MariaDB 中,您可以创建虚拟(计算)列并为其编制索引。新列将通过您的函数填充。这样,不要在WHERE 子句中调用函数,只需使用新列即可。

如果(何时)添加新状态,请执行合适的ALTER TABLE 来重建列和索引。

【讨论】:

  • 我明白了。我的例子被简化了。实际上,除了添加状态之外,还有更多的场景。例如 - 如果我将用户 A 添加到设施 A 并且用户 A 是“管理员”。用户 A 可以查看设施 A 中员工状态为 1、2、3、4 的所有员工。我认为计算列是个好主意,但每次相应部分发生更改时都必须重新计算。计算列可以使用函数或复杂查询、子查询等作为其计算的一部分吗?
  • 我认为它可能涉及存储函数,因此很复杂。我的解决方案与 Brian 的类似,但实现方式不同。
  • 您有使用过程生成列的示例吗?我不确定这是否可能,因为我还没有看到它。此外,我的程序需要输入,因为您是否有权访问员工也取决于您登录的人。这通常是从应用程序传入的,例如SELECT * FROM employee WHERE fn_HAsAccessToEmployee(:session_userid, employee.employeeid)=1
  • 更新了原始问题以提供有关函数性质的更多详细信息。到目前为止,感谢您的意见 Rick
  • 您当前拥有的存储函数(不是过程)可能足以定义新列。但是,由于它确实可以使用SELECT 进入其他表,因此我不确定它是否会起作用。测试一下!
【解决方案2】:

如果我遇到这种情况,我会计算“Employee Has Access”调用的结果,并通过一夜之间的过程将其直接存储在表中(为了安全起见可能已编码)。然后我将触发器附加到影响“Employee Has Access”结果的字段,这样如果值得到更新,结果就会重新计算。

【讨论】:

  • 有趣的方法,绝对值得考虑。谢谢。
  • 我认为这不足之处在于,您是否可以访问员工取决于您是谁以及其他因素(状态等)。我不认为员工表上的列会削减它 - 它本质上必须是另一个表:loggedInUserID、employeeID、status、has_access
  • 也许 - 我在最初的帖子中没有注意到这一点。也就是说,我认为 Rick James 的方法可能更好。
  • 我仍在努力思考这个问题。如果不依赖登录用户等,计算列将是完美的。在您的方法中,为系统的每个用户以及他们有权访问的用户构建缓存似乎有点多。例如,我可能有 1000 个用户和 50000 名员工,我是否要创建一个用户到员工的索引(我假设这将是 1000 * 500000)以存储 1/0 标志?这似乎也不是最好的路径。试图确定最好的解决方法 - 感谢您迄今为止的意见。
  • 除此之外,我认为维护这个矩阵将是一场噩梦(当用户添加了组时,这必须改变,当员工被添加到设施时,必须为此构建员工等)
猜你喜欢
  • 2017-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多