Unit of Work calculates unnecessary UPDATE queries for enum properties in cascaded collections #7059

Closed
opened 2026-01-22 15:43:56 +01:00 by admin · 3 comments
Owner

Originally created by @mbabker on GitHub (Oct 12, 2022).

Bug Report

Q A
BC Break no
Version 2.13.3

Summary

While troubleshooting performance issues in one of our Symfony 6.1 applications, we found a request where a large number of UPDATE queries were being made which were generally unrelated to the actual request, screenshot is from New Relic:

enum-updates-gone-awry

Except for the time_sold column (unrelated to this bug report, it's something I still need to debug), all of the changes in the UPDATE statements are touching columns that use backed enums in their mapping.

After debugging, I've found that this happens when persisting an entity that includes a "cascade persist" on a collection. When the unit of work calculates the change set with one of these entities loaded, it's computing the following change for one of these enum fields:

  [338] =>
  array(1) {
    'status' =>
    array(2) {
      [0] =>
      enum App\LotStatus::AVAILABLE : string("available");
      [1] =>
      string(9) "available"
    }
  }

Current behavior

Unit of Work calculates incorrect changes, causing unnecessary UPDATE statements during a transaction

How to reproduce

https://github.com/mbabker/enum-uow-bug includes the minimal reproducer I was able to build to represent this issue.

Ensuring you have sqlite available on your system, clone the repo and run the following commands:

php app.php orm:schema-tool:create
php app.php orm:schema-tool:update --force
php app.php app:seed-database
php app.php app:update-auction-name --auction-id=1 --name="Changed Name"

When the last command runs, it'll dump out the computed change set from the Unit of Work as well as the log records from the middleware:

array(3) {
  [342] =>
  array(3) {
    'name' =>
    array(2) {
      [0] =>
      string(13) "Testing Event"
      [1] =>
      string(12) "Changed Name"
    }
    'type' =>
    array(2) {
      [0] =>
      enum App\AuctionType::LIVE : string("live");
      [1] =>
      string(4) "live"
    }
    'status' =>
    array(2) {
      [0] =>
      enum App\AuctionStatus::ACTIVE : string("active");
      [1] =>
      string(6) "active"
    }
  }
  [338] =>
  array(1) {
    'status' =>
    array(2) {
      [0] =>
      enum App\LotStatus::AVAILABLE : string("available");
      [1] =>
      string(9) "available"
    }
  }
  [336] =>
  array(1) {
    'status' =>
    array(2) {
      [0] =>
      enum App\LotStatus::AVAILABLE : string("available");
      [1] =>
      string(9) "available"
    }
  }
}
DBAL Log Entries:
 * Connecting with parameters array{"driver":"pdo_sqlite","path":"/Volumes/Samsung T7/Sites/enum-uow-bug/db.sqlite"}
 * Executing statement: SELECT a0_.id AS id_0, a0_.name AS name_1, a0_.type AS type_2, a0_.status AS status_3, l1_.id AS id_4, l1_.number AS number_5, l1_.title AS title_6, l1_.status AS status_7, l1_.auction_id AS auction_id_8 FROM Auction a0_ LEFT JOIN Lot l1_ ON a0_.id = l1_.auction_id WHERE a0_.id = ? (parameters: array{"1":"1"}, types: array{"1":2})
 * Beginning transaction
 * Executing statement: UPDATE Auction SET name = ?, type = ?, status = ? WHERE id = ? (parameters: array{"1":"Changed Name","2":"live","3":"active","4":1}, types: array{"1":2,"2":2,"3":2,"4":1})
 * Executing statement: UPDATE Lot SET status = ? WHERE id = ? (parameters: array{"1":"available","2":1}, types: array{"1":2,"2":1})
 * Executing statement: UPDATE Lot SET status = ? WHERE id = ? (parameters: array{"1":"available","2":2}, types: array{"1":2,"2":1})
 * Committing transaction

Expected behavior

Changes aren't detected for unchanged enum properties, the above reproducer should have the following log output:

DBAL Log Entries:
 * Connecting with parameters array{"driver":"pdo_sqlite","path":"/Volumes/Samsung T7/Sites/enum-uow-bug/db.sqlite"}
 * Executing statement: SELECT a0_.id AS id_0, a0_.name AS name_1, a0_.type AS type_2, a0_.status AS status_3, l1_.id AS id_4, l1_.number AS number_5, l1_.title AS title_6, l1_.status AS status_7, l1_.auction_id AS auction_id_8 FROM Auction a0_ LEFT JOIN Lot l1_ ON a0_.id = l1_.auction_id WHERE a0_.id = ? (parameters: array{"1":"1"}, types: array{"1":2})
 * Beginning transaction
 * Executing statement: UPDATE Auction SET name = ? WHERE id = ? (parameters: array{"1":"Changed Name","2":1}, types: array{"1":2,"2":1})
 * Committing transaction
Originally created by @mbabker on GitHub (Oct 12, 2022). ### Bug Report <!-- Fill in the relevant information below to help triage your issue. --> | Q | A |------------ | ------ | BC Break | no | Version | 2.13.3 #### Summary While troubleshooting performance issues in one of our Symfony 6.1 applications, we found a request where a large number of UPDATE queries were being made which were generally unrelated to the actual request, screenshot is from New Relic: ![enum-updates-gone-awry](https://user-images.githubusercontent.com/368545/195388534-78428c1c-785b-4411-8e46-d32f11180189.png) Except for the `time_sold` column (unrelated to this bug report, it's something I still need to debug), all of the changes in the UPDATE statements are touching columns that use backed enums in their mapping. After debugging, I've found that this happens when persisting an entity that includes a "cascade persist" on a collection. When the unit of work calculates the change set with one of these entities loaded, it's computing the following change for one of these enum fields: ``` [338] => array(1) { 'status' => array(2) { [0] => enum App\LotStatus::AVAILABLE : string("available"); [1] => string(9) "available" } } ``` #### Current behavior Unit of Work calculates incorrect changes, causing unnecessary UPDATE statements during a transaction #### How to reproduce <!-- Provide steps to reproduce the bug. If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc. Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report. --> https://github.com/mbabker/enum-uow-bug includes the minimal reproducer I was able to build to represent this issue. Ensuring you have `sqlite` available on your system, clone the repo and run the following commands: ```sh php app.php orm:schema-tool:create php app.php orm:schema-tool:update --force php app.php app:seed-database php app.php app:update-auction-name --auction-id=1 --name="Changed Name" ``` When the last command runs, it'll dump out the computed change set from the Unit of Work as well as the log records from the middleware: ``` array(3) { [342] => array(3) { 'name' => array(2) { [0] => string(13) "Testing Event" [1] => string(12) "Changed Name" } 'type' => array(2) { [0] => enum App\AuctionType::LIVE : string("live"); [1] => string(4) "live" } 'status' => array(2) { [0] => enum App\AuctionStatus::ACTIVE : string("active"); [1] => string(6) "active" } } [338] => array(1) { 'status' => array(2) { [0] => enum App\LotStatus::AVAILABLE : string("available"); [1] => string(9) "available" } } [336] => array(1) { 'status' => array(2) { [0] => enum App\LotStatus::AVAILABLE : string("available"); [1] => string(9) "available" } } } ``` ``` DBAL Log Entries: * Connecting with parameters array{"driver":"pdo_sqlite","path":"/Volumes/Samsung T7/Sites/enum-uow-bug/db.sqlite"} * Executing statement: SELECT a0_.id AS id_0, a0_.name AS name_1, a0_.type AS type_2, a0_.status AS status_3, l1_.id AS id_4, l1_.number AS number_5, l1_.title AS title_6, l1_.status AS status_7, l1_.auction_id AS auction_id_8 FROM Auction a0_ LEFT JOIN Lot l1_ ON a0_.id = l1_.auction_id WHERE a0_.id = ? (parameters: array{"1":"1"}, types: array{"1":2}) * Beginning transaction * Executing statement: UPDATE Auction SET name = ?, type = ?, status = ? WHERE id = ? (parameters: array{"1":"Changed Name","2":"live","3":"active","4":1}, types: array{"1":2,"2":2,"3":2,"4":1}) * Executing statement: UPDATE Lot SET status = ? WHERE id = ? (parameters: array{"1":"available","2":1}, types: array{"1":2,"2":1}) * Executing statement: UPDATE Lot SET status = ? WHERE id = ? (parameters: array{"1":"available","2":2}, types: array{"1":2,"2":1}) * Committing transaction ``` #### Expected behavior Changes aren't detected for unchanged enum properties, the above reproducer should have the following log output: ``` DBAL Log Entries: * Connecting with parameters array{"driver":"pdo_sqlite","path":"/Volumes/Samsung T7/Sites/enum-uow-bug/db.sqlite"} * Executing statement: SELECT a0_.id AS id_0, a0_.name AS name_1, a0_.type AS type_2, a0_.status AS status_3, l1_.id AS id_4, l1_.number AS number_5, l1_.title AS title_6, l1_.status AS status_7, l1_.auction_id AS auction_id_8 FROM Auction a0_ LEFT JOIN Lot l1_ ON a0_.id = l1_.auction_id WHERE a0_.id = ? (parameters: array{"1":"1"}, types: array{"1":2}) * Beginning transaction * Executing statement: UPDATE Auction SET name = ? WHERE id = ? (parameters: array{"1":"Changed Name","2":1}, types: array{"1":2,"2":1}) * Committing transaction ```
admin closed this issue 2026-01-22 15:43:56 +01:00
Author
Owner

@W0rma commented on GitHub (Oct 13, 2022):

Could that be related to https://github.com/doctrine/orm/pull/10088 ?

@W0rma commented on GitHub (Oct 13, 2022): Could that be related to https://github.com/doctrine/orm/pull/10088 ?
Author
Owner

@mbabker commented on GitHub (Oct 13, 2022):

Possibly related, but I patched my repro locally and it's still giving the extra UPDATE queries with it applied.

@mbabker commented on GitHub (Oct 13, 2022): Possibly related, but I patched my repro locally and it's still giving the extra UPDATE queries with it applied.
Author
Owner

@mbabker commented on GitHub (Nov 23, 2022):

Looks to be fixed on current 2.13 dev build.

@mbabker commented on GitHub (Nov 23, 2022): Looks to be fixed on current 2.13 dev build.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7059